mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
incr: redo duplication (don't sync)
- fix journal entry
This commit is contained in:
parent
fefc79024d
commit
eedb4415ce
@ -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 (
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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)) {
|
||||||
|
@ -113,7 +113,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "writeoff",
|
"fieldname": "writeoff",
|
||||||
"label": "Write Off / Refund",
|
"label": "Write Off",
|
||||||
"fieldtype": "Currency"
|
"fieldtype": "Currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user