Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/models/expense-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class ExpenseModel {
static fromJson(t: any): ExpenseModel {
const m = new ExpenseModel();
m.assignFromObj(t);
m.amount = Number(m.amount.toPrecision(4));

return m;
}
Expand Down
40 changes: 21 additions & 19 deletions src/app/repartition/repartition.component1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,24 @@ function getRepartsFromString(str: string) {

function checkBalanceReportResult(res: any[]) {
expect(res.every(rrr => rrr.eq)).toEqual(true);
expect(res.every(rrr => !rrr.errMsg)).toEqual(true);
expect(res.every(rrr => rrr.totalCostOK)).toEqual(true);
expect(res.every(rrr => rrr.owedOk)).toEqual(true);

// expect(res.every(rrr => rrr.totalCostCalc.toFixed(0) === rrr.totalCost.toFixed(0))).toEqual(true);
expect(res.every(rrr => Math.trunc(rrr.totalCostCalc) === Math.trunc(rrr.totalCost))).toEqual(true);

// expect(res.every(rrr => rrr.totalCostCalc.toPrecision(3) === rrr.totalCost.toPrecision(3))).toEqual(true);

// expect(res.every(rrr => rrr.owed.toFixed(1) === rrr.owedInRepart.toFixed(1))).toEqual(true);
let owedError = false;
if(!res.every(rrr => Math.trunc(rrr.owed) === Math.trunc(rrr.owedInRepart))) {
if(!res.every(rrrr => Math.trunc(rrrr.owedInRepart - rrrr.owed) === Math.trunc(rrrr.totalCost - rrrr.totalCostCalc))) {
// throw new Error("Owed results aren't correct");
owedError = true;
}
}
expect(owedError).toEqual(false);

// let owedError = false;
// if(!res.every(rrr => rrr.owed === rrr.owedInRepart)) {
// if(!res.every(rrrr => (rrrr.owedInRepart - rrrr.owed).toPrecision(4) === (rrrr.totalCost - rrrr.totalCostCalc).toPrecision(4))) {
// // throw new Error("Owed results aren't correct");
// owedError = true;
// }
// }
// expect(owedError).toEqual(false);
}

describe('RepartitionComponent1', () => {
Expand Down Expand Up @@ -208,10 +214,6 @@ describe('RepartitionComponent1', () => {

const res: any[] = RepartitionUtils.checkBalanceRepart(deps, allTricount, false);

expect(res.every(rrr => rrr.eq)).toEqual(true);
expect(res.every(rrr => !rrr.errMsg)).toEqual(true);
expect(res.every(rrr => rrr.totalCostOK)).toEqual(true);

checkBalanceReportResult(res);

// TODO check balance
Expand Down Expand Up @@ -263,7 +265,7 @@ describe('RepartitionComponent1', () => {
const comp = SplitwiseHelper.compareBalances(balReps, balCurrent);
const fails = comp.filter((compItem: IBalanceItem) => {
// return Math.abs(compItem.owed).toFixed(2) !== "0.00" || Math.abs(compItem.owes).toFixed(2) !== "0.00";
return Math.abs(compItem.owed).toFixed(1) !== "0.0" || Math.abs(compItem.owes).toFixed(1) !== "0.0"; // `1` is important
return Number(Math.abs(compItem.owed).toFixed(1)) > 0.5 || Number(Math.abs(compItem.owes).toFixed(1)) > 0.5; // `1` is important
});

expect(fails.length).toEqual(0);
Expand Down Expand Up @@ -295,10 +297,6 @@ describe('RepartitionComponent1', () => {

const res: any[] = RepartitionUtils.checkBalanceRepart(deps, component.allDeps, false);

expect(res.every(rrr => rrr.eq)).toEqual(true);
expect(res.every(rrr => !rrr.errMsg)).toEqual(true);
expect(res.every(rrr => rrr.totalCostOK)).toEqual(true);

checkBalanceReportResult(res);

// const failed = res.filter(rrr => !rrr.totalCostOK); // TODO
Expand All @@ -320,6 +318,10 @@ describe('RepartitionComponent1', () => {
expect(els.length).toEqual(5);

const content: string | null = fixture.debugElement.nativeElement.querySelector("app-balance")?.textContent.trim() || null;
expect(content).toEqual(expecetd);
// expect(content).toEqual(expecetd);

const allContent: IRepartitionItem[] = content !== null ? getRepartsFromString(content) : [];
const allXpected: IRepartitionItem[] = getRepartsFromString(expecetd);
expect(JSON.stringify(allContent)).toEqual(JSON.stringify(allXpected));
});
});
1 change: 1 addition & 0 deletions src/app/utilities/splitwiseHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface IBalanceItem {
}

export class SplitwiseHelper {

private static expenseToInput(expenses: ExpenseModel[]): SplitwiseInputItem[] {
return expenses.map((e: ExpenseModel, _index: number, all: ExpenseModel[]) => {
return {
Expand Down
185 changes: 155 additions & 30 deletions src/test-data/test_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,57 @@ export function checkArray(allDeps: any[], expected: any[], doThrow = true): boo
return hasError;
}

interface ICheckBalanceResult {
user: string
owedInRepart: number
sortiePoche: number
totalCost: number
costListSum: number
eq: boolean
errMsg: string
}

class CheckBalanceResult implements ICheckBalanceResult {
private readonly _owed: number;
private readonly _totalCostCalc: number;
private readonly _totalCostOK: boolean;
private readonly _owedOk: boolean;

get owed(): number {
return this._owed;
}

get totalCostCalc(): number {
return this._totalCostCalc;
}

get totalCostOK(): boolean {
return this._totalCostOK;
}

get owedOk(): boolean {
return this._owedOk;
}

user: string = "";
owedInRepart: number = 0;
sortiePoche: number = 0;
totalCost: number = 0;
costListSum: number = 0;
eq: boolean = false;
errMsg: string = "";

constructor(obj: ICheckBalanceResult) {
Object.assign(this, obj);

this._owed = RepartitionUtils.sortOutNumber(this.sortiePoche - this.totalCost);
this._totalCostCalc = RepartitionUtils.sortOutNumber(this.sortiePoche - this.owedInRepart);
// this.totalCostOK = RepartitionUtils.sortOutNumber(this.totalCostO - this.totalCostCalc);
this._totalCostOK = RepartitionUtils.checkResults([this.totalCost, this._totalCostCalc], "", false).eq;
this._owedOk = RepartitionUtils.checkResults([this.owedInRepart - this._owed, this.totalCost - this._totalCostCalc], "", false).eq;
}
}

export class RepartitionUtils {
static getIPaidForOthers(allExp: ExpenseModel[], me: string): [number, number] {
const targs = allExp.filter(ae => ae.payer === me);
Expand Down Expand Up @@ -195,7 +246,37 @@ export class RepartitionUtils {

return myTravaelCostExpense === myTravaelCostRep;
}
static checkBalanceRepart(expenses: any, allDeps: IRepartitionItem[], doThrow = true) {

/**
* Adjusts a number to the specified digit.
*
* @param {"round" | "floor" | "ceil"} type The type of adjustment.
* @param {number} value The number.
* @param {number} exp The exponent (the 10 logarithm of the adjustment base).
* @returns {number} The adjusted value.
*/
static decimalAdjust(type: "round" | "floor" | "ceil", value: number, exp: number) {
// type = String(type);
if (!["round", "floor", "ceil"].includes(type)) {
throw new TypeError(
"The type of decimal adjustment must be one of 'round', 'floor', or 'ceil'.",
);
}
exp = Number(exp);
value = Number(value);
if (exp % 1 !== 0 || Number.isNaN(value)) {
return NaN;
} else if (exp === 0) {
return Math[type](value);
}
const [magnitude, exponent = 0] = value.toString().split("e");
const adjustedValue = Math[type](Number(`${magnitude}e${Number(exponent) - exp}`));
// Shift back
const [newMagnitude, newExponent = 0] = adjustedValue.toString().split("e");
return Number(`${newMagnitude}e${+newExponent + exp}`);
}

static checkBalanceRepart(expenses: any, allDeps: IRepartitionItem[], doThrow = true): CheckBalanceResult[] {
// TODO type for expenses
const getSumOwedFor = (getFor: string, wereOwed = true): number => {
// return allDeps.filter((p: IRepartitionItem) => wereOwed ? p.owesTo === getFor : p.person === getFor).reduce((res: number, item: IRepartitionItem) => {
Expand Down Expand Up @@ -236,7 +317,7 @@ export class RepartitionUtils {
return ii;
});

const allObj = [];
const allObj: CheckBalanceResult[] = [];
for(const user of users) {
const sortiePoche = expenses.filter((ex: ExpenseModel) => ex.payer === user)
.reduce((res: number, item: ExpenseModel) => res + item.amount, 0);
Expand All @@ -262,46 +343,90 @@ export class RepartitionUtils {
const owedInRepart = getSumOwedFor(user, wereOwed);
// const owedInRepartCorrect = wereOwed ? owedInRepart : (owedInRepart * -1);
const owedInRepartCorrect = owedInRepart;
const eqData = [Math.abs(owedInRepart), Math.abs(sortiePoche - totalCost)];
// const eqData = [owedInRepart, sortiePoche - totalCost];

// const eq = Utils.checkAmounts(eqData[0], eqData[1], 1);
const eq = Math.trunc(eqData[0]) === Math.trunc(eqData[1]);
const eqData: [number, number] = [Math.abs(owedInRepart), Math.abs(sortiePoche - totalCost)];
const {eq, errMsg} = RepartitionUtils.checkResults(eqData, user, doThrow);

const oobj: ICheckBalanceResult = {
user,

console.log(`${user} ${eq} %o`, eqData);
// sortiePoche: sortiePoche,
// totalCost: totalCost,
// costListSum: costListSum,

const first = Number(eqData[0].toFixed(1));
const second = Number(eqData[1].toFixed(1));
// owedInRepart: Number(owedInRepartCorrect.toPrecision(4)),
// sortiePoche: Number(sortiePoche.toPrecision(4)),
// totalCost: Number(totalCost.toPrecision(4)),
// costListSum: Number(costListSum.toPrecision(4)),

let errMsg = "";
if(!eq) {
if(Math.abs(first - second) > 1) {
if (doThrow) {
throw new Error(`Wrong amount check: ${first} !== ${second}`); // TODO don't throw (if used in app)
}
errMsg = `Wrong amount check: ${first} !== ${second}`;
}
}
owedInRepart: RepartitionUtils.sortOutNumber(owedInRepartCorrect),
sortiePoche: RepartitionUtils.sortOutNumber(sortiePoche),
totalCost: RepartitionUtils.sortOutNumber(totalCost),
costListSum: RepartitionUtils.sortOutNumber(costListSum),

const oobj: any = {
user,
sortiePoche: Number(sortiePoche.toFixed(1)),
totalCost: Number(totalCost.toFixed(1)),
costListSum: Number(costListSum.toFixed(1)),
eq,
owedInRepart: Number(owedInRepartCorrect.toFixed(1)),
errMsg
};
oobj.owed = Number((oobj.sortiePoche - oobj.totalCost).toFixed(1));
// oobj.totalCostCalc = oobj.sortiePoche + oobj.owedInRepart;
oobj.totalCostCalc = Number((oobj.sortiePoche - oobj.owedInRepart).toFixed(1));

// oobj.totalCostOK = oobj.totalCost.toFixed(1) === oobj.totalCostCalc.toFixed(1);
oobj.totalCostOK = Math.trunc(oobj.totalCost) === Math.trunc(oobj.totalCostCalc);

allObj.push(oobj);
// oobj.owed = Number((oobj.sortiePoche - oobj.totalCost).toFixed(1));
// // oobj.totalCostCalc = oobj.sortiePoche + oobj.owedInRepart;
// oobj.totalCostCalc = Number((oobj.sortiePoche - oobj.owedInRepart).toFixed(1));
//
// // oobj.totalCostOK = oobj.totalCost.toFixed(1) === oobj.totalCostCalc.toFixed(1);
// oobj.totalCostOK = Math.trunc(oobj.totalCost) === Math.trunc(oobj.totalCostCalc);

// oobj.owed = Number((oobj.sortiePoche - oobj.totalCost).toPrecision(4));
// // oobj.totalCostCalc = Number((oobj.sortiePoche - oobj.owedInRepart).toPrecision(4));
// oobj.totalCostCalc = Number(oobj.sortiePoche.toPrecision(4)) - oobj.owedInRepart;
// // oobj.totalCostOK = oobj.totalCost.toPrecision(3) === oobj.totalCostCalc.toPrecision(3);
// oobj.totalCostOK = RepartitionUtils.decimalAdjust("floor", oobj.totalCost, 0) === RepartitionUtils.decimalAdjust("floor", oobj.totalCostCalc, 0);

// oobj.owed = RepartitionUtils.sortOutNumber(oobj.sortiePoche - oobj.totalCost);
// oobj.totalCostCalc = RepartitionUtils.sortOutNumber(oobj.sortiePoche - oobj.owedInRepart);
// // oobj.totalCostOK = RepartitionUtils.sortOutNumber(oobj.totalCostO - oobj.totalCostCalc);
// oobj.totalCostOK = RepartitionUtils.checkResults([oobj.totalCost, oobj.totalCostCalc], "", false).eq;
// oobj.owedOk = RepartitionUtils.checkResults([oobj.owedInRepart - oobj.owed, oobj.totalCost - oobj.totalCostCalc], "", false).eq;
const myO = new CheckBalanceResult(oobj);

allObj.push(myO);
}

return allObj;
}

static sortOutNumber(number: number): number {
return Math.round((number) * 100) / 100
}

static checkResults(eqData: [number, number], user: string, doThrow: boolean, allowedOffset = 0.5) {
// const eqData = [owedInRepart, sortiePoche - totalCost];

// const eq = Utils.checkAmounts(eqData[0], eqData[1], 1);
// const eq = RepartitionUtils.decimalAdjust("floor", eqData[0], 0) === RepartitionUtils.decimalAdjust("floor", eqData[1], 0);
// const eq = Number(eqData[0].toPrecision(4)) === Number(eqData[1].toPrecision(4));
const first = RepartitionUtils.sortOutNumber(eqData[0]);
const second = RepartitionUtils.sortOutNumber(eqData[1]);
let eq = first === second;

console.log(`${user} ${eq} %o`, eqData);

let errMsg = "";
if(!eq) {
if(Math.abs(first - second) > allowedOffset) {
if (doThrow) {
throw new Error(`Wrong amount check: ${first} !== ${second}`); // TODO don't throw (if used in app)
}
errMsg = `Wrong amount check: ${first} !== ${second}`;
}
else {
eq = true;
}
}

return {
eq,
errMsg
}
}
}