2
0
mirror of https://github.com/frappe/books.git synced 2024-09-19 19:19:02 +00:00

incr: redo duplication (don't sync)

- fix journal entry
This commit is contained in:
18alantom 2022-05-04 18:17:45 +05:30
parent fefc79024d
commit eedb4415ce
9 changed files with 141 additions and 57 deletions

View File

@ -15,7 +15,11 @@ import {
SchemaMap, SchemaMap,
TargetField, TargetField,
} from '../../schemas/types'; } from '../../schemas/types';
import { getRandomString, getValueMapFromList } from '../../utils'; import {
getIsNullOrUndef,
getRandomString,
getValueMapFromList,
} from '../../utils';
import { DatabaseBase, GetAllOptions, QueryFilter } from '../../utils/db/types'; import { DatabaseBase, GetAllOptions, QueryFilter } from '../../utils/db/types';
import { getDefaultMetaFieldValueMap, sqliteTypeMap, SYSTEM } from '../helpers'; import { getDefaultMetaFieldValueMap, sqliteTypeMap, SYSTEM } from '../helpers';
import { import {
@ -889,12 +893,13 @@ export default class DatabaseCore extends DatabaseBase {
const tableFieldValue = fieldValueMap[field.fieldname] as const tableFieldValue = fieldValueMap[field.fieldname] as
| FieldValueMap[] | FieldValueMap[]
| undefined; | undefined
if (tableFieldValue === undefined) { | null;
if (getIsNullOrUndef(tableFieldValue)) {
continue; continue;
} }
for (const child of tableFieldValue) { for (const child of tableFieldValue!) {
this.#prepareChild(schemaName, parentName, child, field, added.length); this.#prepareChild(schemaName, parentName, child, field, added.length);
if ( if (

View File

@ -127,6 +127,10 @@ export class Doc extends Observable<DocValue | Doc[]> {
} else { } else {
this[fieldname] = value ?? this[fieldname] ?? null; this[fieldname] = value ?? this[fieldname] ?? null;
} }
if (field.fieldtype === FieldTypeEnum.Table && !this[fieldname]) {
this[fieldname] = [];
}
} }
} }
@ -269,6 +273,10 @@ export class Doc extends Observable<DocValue | Doc[]> {
} }
_getChildDoc(docValueMap: Doc | DocValueMap, fieldname: string): Doc { _getChildDoc(docValueMap: Doc | DocValueMap, fieldname: string): Doc {
if (!this.name) {
this.name = getRandomString();
}
docValueMap.name ??= getRandomString(); docValueMap.name ??= getRandomString();
// Child Meta Fields // Child Meta Fields
@ -304,6 +312,10 @@ export class Doc extends Observable<DocValue | Doc[]> {
for (const field of tableFields) { for (const field of tableFields) {
const childDocs = this.get(field.fieldname) as Doc[]; const childDocs = this.get(field.fieldname) as Doc[];
if (!childDocs) {
continue;
}
checkForMandatory.push(...childDocs); checkForMandatory.push(...childDocs);
} }
@ -351,13 +363,18 @@ export class Doc extends Observable<DocValue | Doc[]> {
await validator(value); await validator(value);
} }
getValidDict(): DocValueMap { getValidDict(filterMeta: boolean = false): DocValueMap {
let fields = this.schema.fields;
if (filterMeta) {
fields = this.schema.fields.filter((f) => !f.meta);
}
const data: DocValueMap = {}; const data: DocValueMap = {};
for (const field of this.schema.fields) { for (const field of fields) {
let value = this[field.fieldname] as DocValue | DocValueMap[]; let value = this[field.fieldname] as DocValue | DocValueMap[];
if (Array.isArray(value)) { if (Array.isArray(value)) {
value = value.map((doc) => (doc as Doc).getValidDict()); value = value.map((doc) => (doc as Doc).getValidDict(filterMeta));
} }
if (isPesa(value)) { if (isPesa(value)) {
@ -704,25 +721,17 @@ export class Doc extends Observable<DocValue | Doc[]> {
return await this.sync(); return await this.sync();
} }
async duplicate(shouldSync: boolean = true): Promise<Doc> { duplicate(): Doc {
const updateMap: DocValueMap = {}; const updateMap = this.getValidDict(true);
const docValueMap = this.getValidDict(); for (const field in updateMap) {
const fieldnames = this.schema.fields.map((f) => f.fieldname); const value = updateMap[field];
if (!Array.isArray(value)) {
for (const fn of fieldnames) {
const value = docValueMap[fn];
if (getIsNullOrUndef(value)) {
continue; continue;
} }
if (Array.isArray(value)) { for (const row of value) {
value.forEach((row) => {
delete row.name; delete row.name;
delete row.parent;
});
} }
updateMap[fn] = value;
} }
if (this.numberSeries) { if (this.numberSeries) {
@ -731,12 +740,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
updateMap.name = updateMap.name + ' CPY'; updateMap.name = updateMap.name + ' CPY';
} }
const doc = this.fyo.doc.getNewDoc(this.schemaName, updateMap, false); return this.fyo.doc.getNewDoc(this.schemaName, updateMap);
if (shouldSync) {
await doc.sync();
}
return doc;
} }
/** /**

View File

@ -26,7 +26,8 @@ export class LedgerPosting {
fyo: Fyo; fyo: Fyo;
refDoc: Transactional; refDoc: Transactional;
entries: AccountingLedgerEntry[]; entries: AccountingLedgerEntry[];
entryMap: Record<string, AccountingLedgerEntry>; creditMap: Record<string, AccountingLedgerEntry>;
debitMap: Record<string, AccountingLedgerEntry>;
reverted: boolean; reverted: boolean;
accountBalanceChanges: AccountBalanceChange[]; accountBalanceChanges: AccountBalanceChange[];
@ -34,19 +35,20 @@ export class LedgerPosting {
this.fyo = fyo; this.fyo = fyo;
this.refDoc = refDoc; this.refDoc = refDoc;
this.entries = []; this.entries = [];
this.entryMap = {}; this.creditMap = {};
this.debitMap = {};
this.reverted = false; this.reverted = false;
this.accountBalanceChanges = []; this.accountBalanceChanges = [];
} }
async debit(account: string, amount: Money) { async debit(account: string, amount: Money) {
const ledgerEntry = this._getLedgerEntry(account); const ledgerEntry = this._getLedgerEntry(account, 'debit');
await ledgerEntry.set('debit', ledgerEntry.debit!.add(amount)); await ledgerEntry.set('debit', ledgerEntry.debit!.add(amount));
await this._updateAccountBalanceChange(account, 'debit', amount); await this._updateAccountBalanceChange(account, 'debit', amount);
} }
async credit(account: string, amount: Money) { async credit(account: string, amount: Money) {
const ledgerEntry = this._getLedgerEntry(account); const ledgerEntry = this._getLedgerEntry(account, 'credit');
await ledgerEntry.set('credit', ledgerEntry.credit!.add(amount)); await ledgerEntry.set('credit', ledgerEntry.credit!.add(amount));
await this._updateAccountBalanceChange(account, 'credit', amount); await this._updateAccountBalanceChange(account, 'credit', amount);
} }
@ -101,9 +103,17 @@ export class LedgerPosting {
}); });
} }
_getLedgerEntry(account: string): AccountingLedgerEntry { _getLedgerEntry(
if (this.entryMap[account]) { account: string,
return this.entryMap[account]; type: TransactionType
): AccountingLedgerEntry {
let map = this.creditMap;
if (type === 'debit') {
map = this.debitMap;
}
if (map[account]) {
return map[account];
} }
const ledgerEntry = this.fyo.doc.getNewDoc( const ledgerEntry = this.fyo.doc.getNewDoc(
@ -122,9 +132,9 @@ export class LedgerPosting {
) as AccountingLedgerEntry; ) as AccountingLedgerEntry;
this.entries.push(ledgerEntry); this.entries.push(ledgerEntry);
this.entryMap[account] = ledgerEntry; map[account] = ledgerEntry;
return this.entryMap[account]; return map[account];
} }
_validateIsEqual() { _validateIsEqual() {

View File

@ -13,12 +13,12 @@ import Money from 'pesa/dist/types/src/money';
import { LedgerPosting } from '../../Transactional/LedgerPosting'; import { LedgerPosting } from '../../Transactional/LedgerPosting';
export class JournalEntry extends Transactional { export class JournalEntry extends Transactional {
accounts: Doc[] = []; accounts?: Doc[];
async getPosting() { async getPosting() {
const posting: LedgerPosting = new LedgerPosting(this, this.fyo); const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
for (const row of this.accounts) { for (const row of this.accounts ?? []) {
const debit = row.debit as Money; const debit = row.debit as Money;
const credit = row.credit as Money; const credit = row.credit as Money;
const account = row.account as string; const account = row.account as string;

View File

@ -11,9 +11,11 @@ import {
validateEmail, validateEmail,
validatePhoneNumber, validatePhoneNumber,
} from 'fyo/model/validationFunction'; } from 'fyo/model/validationFunction';
import Money from 'pesa/dist/types/src/money';
import { PartyRole } from './types'; import { PartyRole } from './types';
export class Party extends Doc { export class Party extends Doc {
outstandingAmount?: Money;
async updateOutstandingAmount() { async updateOutstandingAmount() {
/** /**
* If Role === "Both" then outstanding Amount * If Role === "Both" then outstanding Amount

View File

@ -73,7 +73,7 @@ export class Payment extends Transactional {
updateAmountOnReferenceUpdate() { updateAmountOnReferenceUpdate() {
this.amount = this.fyo.pesa(0); this.amount = this.fyo.pesa(0);
for (const paymentReference of this.for as Doc[]) { for (const paymentReference of (this.for ?? []) as Doc[]) {
this.amount = (this.amount as Money).add( this.amount = (this.amount as Money).add(
paymentReference.amount as Money paymentReference.amount as Money
); );
@ -163,16 +163,28 @@ export class Payment extends Transactional {
} }
async getPosting() { async getPosting() {
const account = this.account as string; /**
const paymentAccount = this.paymentAccount as string; * account : From Account
const amount = this.amount as Money; * paymentAccount : To Account
const writeoff = this.writeoff as Money; *
* if Receive
* - account : Debtors, etc
* - paymentAccount : Cash, Bank, etc
*
* if Pay
* - account : Cash, Bank, etc
* - paymentAccount : Creditors, etc
*/
const posting: LedgerPosting = new LedgerPosting(this, this.fyo); const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
await posting.debit(paymentAccount as string, amount.sub(writeoff)); const paymentAccount = this.paymentAccount as string;
await posting.credit(account as string, amount.sub(writeoff)); const account = this.account as string;
const amount = this.amount as Money;
this.applyWriteOffPosting(posting); await posting.debit(paymentAccount as string, amount);
await posting.credit(account as string, amount);
await this.applyWriteOffPosting(posting);
return posting; return posting;
} }
@ -183,11 +195,12 @@ export class Payment extends Transactional {
} }
const account = this.account as string; const account = this.account as string;
const paymentAccount = this.paymentAccount as string;
const writeOffAccount = this.fyo.singles.AccountingSettings! const writeOffAccount = this.fyo.singles.AccountingSettings!
.writeOffAccount as string; .writeOffAccount as string;
if (this.paymentType === 'Pay') { if (this.paymentType === 'Pay') {
await posting.credit(account, writeoff); await posting.credit(paymentAccount, writeoff);
await posting.debit(writeOffAccount, writeoff); await posting.debit(writeOffAccount, writeoff);
} else { } else {
await posting.debit(account, writeoff); await posting.debit(account, writeoff);
@ -277,7 +290,7 @@ export class Payment extends Transactional {
} }
async _revertReferenceOutstanding() { async _revertReferenceOutstanding() {
for (const ref of this.for as PaymentFor[]) { for (const ref of (this.for ?? []) as PaymentFor[]) {
const refDoc = await this.fyo.doc.getDoc( const refDoc = await this.fyo.doc.getDoc(
ref.referenceType!, ref.referenceType!,
ref.referenceName! ref.referenceName!
@ -318,6 +331,32 @@ export class Payment extends Transactional {
}, },
dependsOn: ['paymentMethod', 'paymentType'], dependsOn: ['paymentMethod', 'paymentType'],
}, },
paymentType: {
formula: async () => {
if (!this.party) {
return;
}
const partyDoc = await this.fyo.doc.getDoc(
ModelNameEnum.Party,
this.party
);
if (partyDoc.role === 'Supplier') {
return 'Pay';
} else if (partyDoc.role === 'Customer') {
return 'Receive';
}
const outstanding = partyDoc.outstandingAmount as Money;
if (outstanding?.isZero() ?? true) {
return '';
}
if (outstanding?.isPositive()) {
return 'Receive';
}
return 'Pay';
},
},
amount: { amount: {
formula: async () => this.getSum('for', 'amount', false), formula: async () => this.getSum('for', 'amount', false),
dependsOn: ['for'], dependsOn: ['for'],
@ -332,7 +371,10 @@ export class Payment extends Transactional {
); );
} }
if ((this.for as Doc[]).length === 0) return; if (((this.for ?? []) as Doc[]).length === 0) {
return;
}
const amount = this.getSum('for', 'amount', false); const amount = this.getSum('for', 'amount', false);
if ((value as Money).gt(amount)) { if ((value as Money).gt(amount)) {

View File

@ -113,7 +113,7 @@
}, },
{ {
"fieldname": "writeoff", "fieldname": "writeoff",
"label": "Write Off / Refund", "label": "Write Off",
"fieldtype": "Currency" "fieldtype": "Currency"
}, },
{ {

View File

@ -21,7 +21,11 @@
</Row> </Row>
<!-- Data Rows --> <!-- Data Rows -->
<div class="overflow-auto" :style="{ 'max-height': maxHeight }"> <div
class="overflow-auto"
:style="{ 'max-height': maxHeight }"
v-if="value"
>
<TableRow <TableRow
:class="{ 'pointer-events-none': isReadOnly }" :class="{ 'pointer-events-none': isReadOnly }"
ref="table-row" ref="table-row"
@ -58,7 +62,9 @@
'px-2 py-3': size === 'small', 'px-2 py-3': size === 'small',
'px-3 py-4': size !== 'small', 'px-3 py-4': size !== 'small',
}" }"
v-if="maxRowsBeforeOverflow && value.length > maxRowsBeforeOverflow" v-if="
value && maxRowsBeforeOverflow && value.length > maxRowsBeforeOverflow
"
> >
{{ t`${value.length} rows` }} {{ t`${value.length} rows` }}
</div> </div>

View File

@ -7,6 +7,7 @@ import { t } from 'fyo';
import { Doc } from 'fyo/model/doc'; import { Doc } from 'fyo/model/doc';
import { Action } from 'fyo/model/types'; import { Action } from 'fyo/model/types';
import { getActions } from 'fyo/utils'; import { getActions } from 'fyo/utils';
import { ModelNameEnum } from 'models/types';
import { handleErrorWithDialog } from 'src/errorHandling'; import { handleErrorWithDialog } from 'src/errorHandling';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import router from 'src/router'; import router from 'src/router';
@ -288,15 +289,28 @@ function getDeleteAction(doc: Doc): Action {
}; };
} }
async function openEdit(doc: Doc) {
const isFormEdit = [
ModelNameEnum.SalesInvoice,
ModelNameEnum.PurchaseInvoice,
ModelNameEnum.JournalEntry,
].includes(doc.schemaName as ModelNameEnum);
if (isFormEdit) {
return await routeTo(`/edit/${doc.schemaName}/${doc.name!}`);
}
await openQuickEdit({ schemaName: doc.schemaName, name: doc.name! });
}
function getDuplicateAction(doc: Doc): Action { function getDuplicateAction(doc: Doc): Action {
const isSubmittable = !!doc.schema.isSubmittable; const isSubmittable = !!doc.schema.isSubmittable;
return { return {
label: t`Duplicate`, label: t`Duplicate`,
condition: (doc: Doc) => condition: (doc: Doc) =>
!!( !!(
((isSubmittable && doc && doc.submitted) || !isSubmittable) && ((isSubmittable && doc.submitted) || !isSubmittable) &&
!doc.notInserted && !doc.notInserted
!(doc.cancelled || false)
), ),
async action() { async action() {
await showMessageDialog({ await showMessageDialog({
@ -306,7 +320,8 @@ function getDuplicateAction(doc: Doc): Action {
label: t`Yes`, label: t`Yes`,
async action() { async action() {
try { try {
doc.duplicate(); const dupe = await doc.duplicate();
await openEdit(dupe);
return true; return true;
} catch (err) { } catch (err) {
handleErrorWithDialog(err as Error, doc); handleErrorWithDialog(err as Error, doc);