mirror of
https://github.com/frappe/books.git
synced 2024-12-23 03:19:01 +00:00
Merge pull request #649 from frappe/create-invoice-from-stock-transfer
feat: create invoice from stock transfer
This commit is contained in:
commit
081ad7a38f
@ -45,6 +45,7 @@ export abstract class Invoice extends Transactional {
|
||||
discountPercent?: number;
|
||||
discountAfterTax?: boolean;
|
||||
stockNotTransferred?: number;
|
||||
backReference?: string;
|
||||
|
||||
submitted?: boolean;
|
||||
cancelled?: boolean;
|
||||
@ -479,6 +480,7 @@ export abstract class Invoice extends Transactional {
|
||||
terms: () => !(this.terms || !(this.isSubmitted || this.isCancelled)),
|
||||
attachment: () =>
|
||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||
backReference: () => !this.backReference,
|
||||
};
|
||||
|
||||
static defaults: DefaultMap = {
|
||||
@ -585,16 +587,20 @@ export abstract class Invoice extends Transactional {
|
||||
|
||||
const defaults = (this.fyo.singles.Defaults as Defaults) ?? {};
|
||||
let terms;
|
||||
let numberSeries;
|
||||
if (this.isSales) {
|
||||
terms = defaults.shipmentTerms ?? '';
|
||||
numberSeries = defaults.shipmentNumberSeries ?? undefined;
|
||||
} else {
|
||||
terms = defaults.purchaseReceiptTerms ?? '';
|
||||
numberSeries = defaults.purchaseReceiptNumberSeries ?? undefined;
|
||||
}
|
||||
|
||||
const data = {
|
||||
party: this.party,
|
||||
date: new Date().toISOString(),
|
||||
terms,
|
||||
numberSeries,
|
||||
backReference: this.name,
|
||||
};
|
||||
|
||||
@ -613,6 +619,8 @@ export abstract class Invoice extends Transactional {
|
||||
const quantity = row.stockNotTransferred;
|
||||
const trackItem = itemDoc.trackItem;
|
||||
const batch = row.batch || null;
|
||||
const description = row.description;
|
||||
const hsnCode = row.hsnCode;
|
||||
let rate = row.rate as Money;
|
||||
|
||||
if (this.exchangeRate && this.exchangeRate > 1) {
|
||||
@ -629,6 +637,8 @@ export abstract class Invoice extends Transactional {
|
||||
location,
|
||||
rate,
|
||||
batch,
|
||||
description,
|
||||
hsnCode,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import { FieldTypeEnum, Schema } from 'schemas/types';
|
||||
import { safeParseFloat } from 'utils/index';
|
||||
import { Invoice } from '../Invoice/Invoice';
|
||||
import { Item } from '../Item/Item';
|
||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||
|
||||
export abstract class InvoiceItem extends Doc {
|
||||
item?: string;
|
||||
@ -24,6 +25,9 @@ export abstract class InvoiceItem extends Doc {
|
||||
parentdoc?: Invoice;
|
||||
rate?: Money;
|
||||
|
||||
description?: string;
|
||||
hsnCode?: number;
|
||||
|
||||
unit?: string;
|
||||
transferUnit?: string;
|
||||
quantity?: number;
|
||||
@ -347,7 +351,26 @@ export abstract class InvoiceItem extends Doc {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.quantity;
|
||||
const { backReference, stockTransferSchemaName } = this.parentdoc ?? {};
|
||||
if (
|
||||
!backReference ||
|
||||
!stockTransferSchemaName ||
|
||||
typeof this.quantity !== 'number'
|
||||
) {
|
||||
return this.quantity;
|
||||
}
|
||||
|
||||
const refdoc = (await this.fyo.doc.getDoc(
|
||||
stockTransferSchemaName,
|
||||
backReference
|
||||
)) as StockTransfer;
|
||||
|
||||
const transferred =
|
||||
refdoc.items
|
||||
?.filter((i) => i.item === this.item)
|
||||
.reduce((acc, i) => i.quantity ?? 0 + acc, 0) ?? 0;
|
||||
|
||||
return Math.max(0, this.quantity - transferred);
|
||||
},
|
||||
dependsOn: ['item', 'quantity'],
|
||||
},
|
||||
|
@ -29,6 +29,17 @@ export function getInvoiceActions(
|
||||
];
|
||||
}
|
||||
|
||||
export function getStockTransferActions(
|
||||
fyo: Fyo,
|
||||
schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt
|
||||
): Action[] {
|
||||
return [
|
||||
getMakeInvoiceAction(fyo, schemaName),
|
||||
getLedgerLinkAction(fyo, false),
|
||||
getLedgerLinkAction(fyo, true),
|
||||
];
|
||||
}
|
||||
|
||||
export function getMakeStockTransferAction(
|
||||
fyo: Fyo,
|
||||
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
|
||||
@ -55,6 +66,32 @@ export function getMakeStockTransferAction(
|
||||
};
|
||||
}
|
||||
|
||||
export function getMakeInvoiceAction(
|
||||
fyo: Fyo,
|
||||
schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt
|
||||
): Action {
|
||||
let label = fyo.t`Sales Invoice`;
|
||||
if (schemaName === ModelNameEnum.PurchaseReceipt) {
|
||||
label = fyo.t`Purchase Invoice`;
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
group: fyo.t`Create`,
|
||||
condition: (doc: Doc) => doc.isSubmitted && !doc.backReference,
|
||||
action: async (doc: Doc) => {
|
||||
const invoice = await (doc as StockTransfer).getInvoice();
|
||||
if (!invoice) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { routeTo } = await import('src/utils/ui');
|
||||
const path = `/edit/${invoice.schemaName}/${invoice.name}`;
|
||||
await routeTo(path);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getMakePaymentAction(fyo: Fyo): Action {
|
||||
return {
|
||||
label: fyo.t`Payment`,
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { ListViewSettings } from 'fyo/model/types';
|
||||
import { getTransactionStatusColumn } from 'models/helpers';
|
||||
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||
import {
|
||||
getStockTransferActions,
|
||||
getTransactionStatusColumn,
|
||||
} from 'models/helpers';
|
||||
import { PurchaseReceiptItem } from './PurchaseReceiptItem';
|
||||
import { StockTransfer } from './StockTransfer';
|
||||
import { Fyo } from 'fyo';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
|
||||
export class PurchaseReceipt extends StockTransfer {
|
||||
items?: PurchaseReceiptItem[];
|
||||
@ -17,4 +22,8 @@ export class PurchaseReceipt extends StockTransfer {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static getActions(fyo: Fyo): Action[] {
|
||||
return getStockTransferActions(fyo, ModelNameEnum.Shipment);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { ListViewSettings } from 'fyo/model/types';
|
||||
import { getTransactionStatusColumn } from 'models/helpers';
|
||||
import { Fyo } from 'fyo';
|
||||
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||
import {
|
||||
getStockTransferActions,
|
||||
getTransactionStatusColumn,
|
||||
} from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { ShipmentItem } from './ShipmentItem';
|
||||
import { StockTransfer } from './StockTransfer';
|
||||
|
||||
@ -17,4 +22,8 @@ export class Shipment extends StockTransfer {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static getActions(fyo: Fyo): Action[] {
|
||||
return getStockTransferActions(fyo, ModelNameEnum.Shipment);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { t } from 'fyo';
|
||||
import { Attachment } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import {
|
||||
Action,
|
||||
ChangeArg,
|
||||
DefaultMap,
|
||||
FiltersMap,
|
||||
@ -13,7 +12,7 @@ import { ValidationError } from 'fyo/utils/errors';
|
||||
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
||||
import { Defaults } from 'models/baseModels/Defaults/Defaults';
|
||||
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
||||
import { addItem, getLedgerLinkAction, getNumberSeries } from 'models/helpers';
|
||||
import { addItem, getNumberSeries } from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
import { TargetField } from 'schemas/types';
|
||||
@ -27,6 +26,7 @@ import {
|
||||
validateBatch,
|
||||
validateSerialNumber,
|
||||
} from './helpers';
|
||||
import { Item } from 'models/baseModels/Item/Item';
|
||||
|
||||
export abstract class StockTransfer extends Transfer {
|
||||
name?: string;
|
||||
@ -42,6 +42,13 @@ export abstract class StockTransfer extends Transfer {
|
||||
return this.schemaName === ModelNameEnum.Shipment;
|
||||
}
|
||||
|
||||
get invoiceSchemaName() {
|
||||
if (this.isSales) {
|
||||
return ModelNameEnum.SalesInvoice;
|
||||
}
|
||||
return ModelNameEnum.PurchaseInvoice;
|
||||
}
|
||||
|
||||
formulas: FormulaMap = {
|
||||
grandTotal: {
|
||||
formula: () => this.getSum('items', 'amount', false),
|
||||
@ -174,10 +181,6 @@ export abstract class StockTransfer extends Transfer {
|
||||
await validateSerialNumberStatus(this);
|
||||
}
|
||||
|
||||
static getActions(fyo: Fyo): Action[] {
|
||||
return [getLedgerLinkAction(fyo, false), getLedgerLinkAction(fyo, true)];
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
await super.afterSubmit();
|
||||
await updateSerialNumbers(this, false);
|
||||
@ -309,6 +312,68 @@ export abstract class StockTransfer extends Transfer {
|
||||
await this.set('date', stDoc.date);
|
||||
await this.set('items', stDoc.items);
|
||||
}
|
||||
|
||||
async getInvoice(): Promise<Invoice | null> {
|
||||
if (!this.isSubmitted || this.backReference) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const schemaName = this.invoiceSchemaName;
|
||||
|
||||
const defaults = (this.fyo.singles.Defaults as Defaults) ?? {};
|
||||
let terms;
|
||||
let numberSeries;
|
||||
if (this.isSales) {
|
||||
terms = defaults.salesInvoiceTerms ?? '';
|
||||
numberSeries = defaults.salesInvoiceNumberSeries ?? undefined;
|
||||
} else {
|
||||
terms = defaults.purchaseInvoiceTerms ?? '';
|
||||
numberSeries = defaults.purchaseInvoiceNumberSeries ?? undefined;
|
||||
}
|
||||
|
||||
const data = {
|
||||
party: this.party,
|
||||
date: new Date().toISOString(),
|
||||
terms,
|
||||
numberSeries,
|
||||
backReference: this.name,
|
||||
};
|
||||
|
||||
const invoice = this.fyo.doc.getNewDoc(schemaName, data) as Invoice;
|
||||
for (const row of this.items ?? []) {
|
||||
if (!row.item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const item = row.item;
|
||||
const unit = row.unit;
|
||||
const quantity = row.quantity;
|
||||
const batch = row.batch || null;
|
||||
const rate = row.rate ?? this.fyo.pesa(0);
|
||||
const description = row.description;
|
||||
const hsnCode = row.hsnCode;
|
||||
|
||||
if (!quantity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await invoice.append('items', {
|
||||
item,
|
||||
quantity,
|
||||
unit,
|
||||
rate,
|
||||
batch,
|
||||
hsnCode,
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
if (!invoice.items?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return invoice;
|
||||
}
|
||||
}
|
||||
|
||||
async function validateSerialNumberStatus(doc: StockTransfer) {
|
||||
|
@ -174,6 +174,11 @@
|
||||
"label": "Attachment",
|
||||
"fieldtype": "Attachment",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"abstract": true,
|
||||
"fieldname": "backReference",
|
||||
"section": "References"
|
||||
}
|
||||
],
|
||||
"keywordFields": ["name", "party"]
|
||||
|
@ -15,6 +15,13 @@
|
||||
"default": "PINV-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "backReference",
|
||||
"label": "Back Reference",
|
||||
"fieldtype": "Link",
|
||||
"target": "PurchaseReceipt",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
|
@ -15,6 +15,13 @@
|
||||
"default": "SINV-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "backReference",
|
||||
"label": "Back Reference",
|
||||
"fieldtype": "Link",
|
||||
"target": "Shipment",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
|
Loading…
Reference in New Issue
Block a user