Skip to content

Commit fdbb907

Browse files
committed
Sales taxes import implemented
1 parent 466661f commit fdbb907

File tree

6 files changed

+133
-15
lines changed

6 files changed

+133
-15
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"start": "node dist/index.js -server",
3232
"start:dev": "concurrently \"yarn server:dev\" \"yarn client:dev\"",
3333
"storybook": "storybook dev -p 6006",
34-
"storybook:build": "storybook build -o dist/storybook"
34+
"storybook:build": "storybook build -o dist/storybook",
35+
"updater": "node dist/index.js -watch -update"
3536
},
3637
"dependencies": {
3738
"@chakra-ui/icons": "^2.2.4",

src/bots/importer.bot.ts

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
InterestStatement,
1313
OptionStatement,
1414
Portfolio,
15+
SalesTaxes,
1516
Statement,
1617
StatementStatus,
1718
TaxStatement,
@@ -70,8 +71,10 @@ const transactionStatusFromElement = (element: any): StatementStatus => {
7071
else if (element.notes == "Ca")
7172
return StatementStatus.CANCELLED_STATUS; // Cancelled
7273
else if (element.openCloseIndicator == "O") return StatementStatus.OPEN_STATUS;
74+
else if (element.openCloseIndicator == "C;O") return StatementStatus.OPEN_STATUS;
7375
else if (element.openCloseIndicator == "C") return StatementStatus.CLOSE_STATUS;
7476
else if (element.assetCategory == "CASH") return StatementStatus.UNDEFINED_STATUS;
77+
else if (element.openCloseIndicator === undefined) return StatementStatus.UNDEFINED_STATUS;
7578
else {
7679
logger.error(MODULE + ".transactionStatusFromElement", "unknown status: ", element);
7780
throw Error("undefined status");
@@ -95,7 +98,7 @@ const transactionDescriptionFromElement = (element: any): string => {
9598
let description = "";
9699
switch (transactionStatusFromElement(element)) {
97100
case StatementStatus.ASSIGNED_STATUS:
98-
console.log(MODULE + ".transactionDescriptionFromElement", element);
101+
// console.log(MODULE + ".transactionDescriptionFromElement", element);
99102
description += "Assigned";
100103
break;
101104
case StatementStatus.EXPIRED_STATUS:
@@ -304,6 +307,7 @@ export class ImporterBot extends ITradingBot {
304307
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
305308
protected async processStockTrade(element: any): Promise<void> {
306309
logger.log(LogLevel.Trace, MODULE + ".processStockTrade", element.symbol as string, element);
310+
// if (element.symbol == "SHYL") console.log(element);
307311
return this.findOrCreateContract(ibContractFromElement(element)).then(async (contract) =>
308312
Statement.findOrCreate({
309313
where: { transactionId: element.transactionID },
@@ -814,17 +818,103 @@ export class ImporterBot extends ITradingBot {
814818
});
815819
}
816820

821+
/*
822+
{
823+
"accountId": "***",
824+
"acctAlias": "",
825+
"model": "",
826+
"currency": "EUR",
827+
"fxRateToBase": "1",
828+
"assetCategory": "",
829+
"subCategory": "",
830+
"symbol": "",
831+
"description": "",
832+
"conid": "",
833+
"securityID": "",
834+
"securityIDType": "",
835+
"cusip": "",
836+
"isin": "",
837+
"figi": "",
838+
"listingExchange": "",
839+
"underlyingConid": "",
840+
"underlyingSymbol": "",
841+
"underlyingSecurityID": "",
842+
"underlyingListingExchange": "",
843+
"issuer": "",
844+
"issuerCountryCode": "",
845+
"multiplier": "",
846+
"strike": "",
847+
"expiry": "",
848+
"putCall": "",
849+
"principalAdjustFactor": "",
850+
"date": "2024-08-02",
851+
"country": "France",
852+
"taxType": "VAT",
853+
"payer": "U7802803",
854+
"taxableDescription": "r******99:COMEX(Globex)(NP,L2)",
855+
"taxableAmount": "-10.16",
856+
"taxRate": "0.2",
857+
"salesTax": "-2.032",
858+
"taxableTransactionID": "2947603793",
859+
"transactionID": "3220505865",
860+
"code": "",
861+
"serialNumber": "",
862+
"deliveryType": "",
863+
"commodityType": "",
864+
"fineness": "",
865+
"weight": ""
866+
}
867+
*/
817868
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
818869
protected async processSalesTax(element: any): Promise<void> {
819870
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
820871
logger.log(LogLevel.Trace, MODULE + ".processSalesTaxes", element.symbol, element);
821-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
822-
logger.log(LogLevel.Warning, MODULE + ".processSalesTaxes", element.symbol, "not implemented", element);
872+
const taxableTransaction = await Statement.findOne({
873+
where: { transactionId: element.taxableTransactionID },
874+
});
875+
if (taxableTransaction) {
876+
return Statement.findOrCreate({
877+
where: { transactionId: element.transactionID },
878+
defaults: {
879+
portfolio_id: this.portfolio.id,
880+
statementType: StatementTypes.SalesTaxStatement,
881+
date: taxableTransaction.date,
882+
currency: element.currency,
883+
netCash: element.salesTax,
884+
description: `${element.country} ${element.taxType} tax for ${element.taxableDescription}`,
885+
transactionId: element.transactionID,
886+
fxRateToBase: element.fxRateToBase,
887+
},
888+
})
889+
.then(async ([statement, _created]) => {
890+
return SalesTaxes.findOrCreate({
891+
where: { id: statement.id },
892+
defaults: {
893+
id: statement.id,
894+
taxableTransactionID: taxableTransaction.id,
895+
country: element.country,
896+
taxType: element.taxType,
897+
taxableAmount: element.taxableAmount,
898+
taxRate: element.taxRate,
899+
salesTax: element.salesTax,
900+
},
901+
});
902+
})
903+
.then(([_taxStatement, _created]): void => undefined);
904+
} else {
905+
logger.log(
906+
LogLevel.Error,
907+
MODULE + ".processSalesTaxes",
908+
element.symbol as string,
909+
"taxableTransactionID not found",
910+
element.taxableTransactionID,
911+
);
912+
}
913+
823914
return Promise.resolve();
824915
}
825916

826917
private async processAllSalesTaxes(flexReport: FlexStatement): Promise<FlexStatement> {
827-
console.debug("processAllSalesTaxes", flexReport);
828918
let elements: any[];
829919
if (!flexReport.SalesTaxes || !flexReport.SalesTaxes.SalesTax) {
830920
elements = [];

src/models/sales_taxes.model.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CreationOptional, InferAttributes, InferCreationAttributes } from "sequelize";
2-
import { BelongsTo, Column, DataType, Model, Table } from "sequelize-typescript";
2+
import { BelongsTo, Column, DataType, ForeignKey, Model, Table } from "sequelize-typescript";
33
import { Statement } from "./statement.model";
44

55
@Table({})
@@ -12,10 +12,26 @@ export class SalesTaxes extends Model<InferAttributes<SalesTaxes>, InferCreation
1212
// updatedAt can be undefined during creation
1313
declare updatedAt: CreationOptional<Date>;
1414

15-
@Column({ type: DataType.INTEGER })
16-
declare transactionId: number;
17-
1815
/** Underlying transaction */
19-
@BelongsTo(() => Statement, "id")
20-
declare underlying: Statement;
16+
// declare taxableTransactionID: ForeignKey<Contract["Statement"]>;
17+
@ForeignKey(() => Statement)
18+
@Column
19+
declare taxableTransactionID: number;
20+
@BelongsTo(() => Statement, "taxableTransactionID")
21+
declare taxableTransaction: CreationOptional<Statement>;
22+
23+
@Column({ type: DataType.STRING })
24+
declare country: string;
25+
26+
@Column({ type: DataType.STRING })
27+
declare taxType: string;
28+
29+
@Column({ type: DataType.FLOAT })
30+
declare taxableAmount: number;
31+
32+
@Column({ type: DataType.FLOAT })
33+
declare taxRate: number;
34+
35+
@Column({ type: DataType.FLOAT })
36+
declare salesTax: number;
2137
}

src/models/statement.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ export const StatementTypes = {
99
CorporateStatement: "CorporateStatement",
1010
CashStatement: "Cash",
1111
BondStatement: "Bond",
12+
SalesTaxStatement: "SalesTax",
1213
} as const;
1314
export type StatementTypes = (typeof StatementTypes)[keyof typeof StatementTypes];

src/routers/statements.types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type StatementUnderlyingOption = StatementUnderlyingEntry & {
2323
export type BaseStatement = {
2424
id: number;
2525
transactionId: number;
26+
// statementType: StatementTypes;
2627
date: number;
2728
currency: string;
2829
fxRateToBase: number;
@@ -84,6 +85,8 @@ export type CorporateStatementEntry = BaseStatement & {
8485

8586
export type CashStatementEntry = BaseStatement & { statementType: "Cash" };
8687

88+
export type SalesTaxStatementEntry = BaseStatement & { statementType: "SalesTax" };
89+
8790
export type StatementEntry =
8891
| EquityStatementEntry
8992
| OptionStatementEntry
@@ -94,4 +97,5 @@ export type StatementEntry =
9497
| FeeStatementEntry
9598
| CorporateStatementEntry
9699
| CashStatementEntry
97-
| BondStatementEntry;
100+
| BondStatementEntry
101+
| SalesTaxStatementEntry;

src/routers/statements.utils.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const statementModelToStatementEntry = async (item: Statement): Promise<S
2020
id: item.id,
2121
transactionId: item.transactionId,
2222
date: item.date.getTime(),
23+
// statementType: item.statementType,
2324
currency: item.currency,
2425
amount: item.netCash,
2526
fxRateToBase: item.fxRateToBase,
@@ -147,10 +148,10 @@ export const statementModelToStatementEntry = async (item: Statement): Promise<S
147148
});
148149

149150
case StatementTypes.CashStatement:
150-
return Promise.resolve({
151+
return {
151152
statementType: StatementTypes.CashStatement,
152153
...baseStatement,
153-
});
154+
};
154155

155156
case StatementTypes.BondStatement:
156157
return BondStatement.findByPk(item.id).then((thisStatement) => {
@@ -164,13 +165,18 @@ export const statementModelToStatementEntry = async (item: Statement): Promise<S
164165
...baseStatement,
165166
country,
166167
underlying: item.stock,
167-
// accruedInterests: thisStatement.accruedInterests,
168168
quantity: thisStatement.quantity,
169169
pnl: thisStatement.realizedPnL,
170170
fees: thisStatement.fees,
171171
};
172172
});
173173

174+
case StatementTypes.SalesTaxStatement:
175+
return {
176+
statementType: StatementTypes.SalesTaxStatement,
177+
...baseStatement,
178+
};
179+
174180
default:
175181
throw Error("Undefined statement type");
176182
}

0 commit comments

Comments
 (0)