mirror of
https://github.com/frappe/books.git
synced 2025-01-10 18:24:40 +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;
|
discountPercent?: number;
|
||||||
discountAfterTax?: boolean;
|
discountAfterTax?: boolean;
|
||||||
stockNotTransferred?: number;
|
stockNotTransferred?: number;
|
||||||
|
backReference?: string;
|
||||||
|
|
||||||
submitted?: boolean;
|
submitted?: boolean;
|
||||||
cancelled?: boolean;
|
cancelled?: boolean;
|
||||||
@ -479,6 +480,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
terms: () => !(this.terms || !(this.isSubmitted || this.isCancelled)),
|
terms: () => !(this.terms || !(this.isSubmitted || this.isCancelled)),
|
||||||
attachment: () =>
|
attachment: () =>
|
||||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||||
|
backReference: () => !this.backReference,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaults: DefaultMap = {
|
static defaults: DefaultMap = {
|
||||||
@ -585,16 +587,20 @@ export abstract class Invoice extends Transactional {
|
|||||||
|
|
||||||
const defaults = (this.fyo.singles.Defaults as Defaults) ?? {};
|
const defaults = (this.fyo.singles.Defaults as Defaults) ?? {};
|
||||||
let terms;
|
let terms;
|
||||||
|
let numberSeries;
|
||||||
if (this.isSales) {
|
if (this.isSales) {
|
||||||
terms = defaults.shipmentTerms ?? '';
|
terms = defaults.shipmentTerms ?? '';
|
||||||
|
numberSeries = defaults.shipmentNumberSeries ?? undefined;
|
||||||
} else {
|
} else {
|
||||||
terms = defaults.purchaseReceiptTerms ?? '';
|
terms = defaults.purchaseReceiptTerms ?? '';
|
||||||
|
numberSeries = defaults.purchaseReceiptNumberSeries ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
party: this.party,
|
party: this.party,
|
||||||
date: new Date().toISOString(),
|
date: new Date().toISOString(),
|
||||||
terms,
|
terms,
|
||||||
|
numberSeries,
|
||||||
backReference: this.name,
|
backReference: this.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -613,6 +619,8 @@ export abstract class Invoice extends Transactional {
|
|||||||
const quantity = row.stockNotTransferred;
|
const quantity = row.stockNotTransferred;
|
||||||
const trackItem = itemDoc.trackItem;
|
const trackItem = itemDoc.trackItem;
|
||||||
const batch = row.batch || null;
|
const batch = row.batch || null;
|
||||||
|
const description = row.description;
|
||||||
|
const hsnCode = row.hsnCode;
|
||||||
let rate = row.rate as Money;
|
let rate = row.rate as Money;
|
||||||
|
|
||||||
if (this.exchangeRate && this.exchangeRate > 1) {
|
if (this.exchangeRate && this.exchangeRate > 1) {
|
||||||
@ -629,6 +637,8 @@ export abstract class Invoice extends Transactional {
|
|||||||
location,
|
location,
|
||||||
rate,
|
rate,
|
||||||
batch,
|
batch,
|
||||||
|
description,
|
||||||
|
hsnCode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import { FieldTypeEnum, Schema } from 'schemas/types';
|
|||||||
import { safeParseFloat } from 'utils/index';
|
import { safeParseFloat } from 'utils/index';
|
||||||
import { Invoice } from '../Invoice/Invoice';
|
import { Invoice } from '../Invoice/Invoice';
|
||||||
import { Item } from '../Item/Item';
|
import { Item } from '../Item/Item';
|
||||||
|
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||||
|
|
||||||
export abstract class InvoiceItem extends Doc {
|
export abstract class InvoiceItem extends Doc {
|
||||||
item?: string;
|
item?: string;
|
||||||
@ -24,6 +25,9 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
parentdoc?: Invoice;
|
parentdoc?: Invoice;
|
||||||
rate?: Money;
|
rate?: Money;
|
||||||
|
|
||||||
|
description?: string;
|
||||||
|
hsnCode?: number;
|
||||||
|
|
||||||
unit?: string;
|
unit?: string;
|
||||||
transferUnit?: string;
|
transferUnit?: string;
|
||||||
quantity?: number;
|
quantity?: number;
|
||||||
@ -347,7 +351,26 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
return 0;
|
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'],
|
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(
|
export function getMakeStockTransferAction(
|
||||||
fyo: Fyo,
|
fyo: Fyo,
|
||||||
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
|
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 {
|
export function getMakePaymentAction(fyo: Fyo): Action {
|
||||||
return {
|
return {
|
||||||
label: fyo.t`Payment`,
|
label: fyo.t`Payment`,
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { ListViewSettings } from 'fyo/model/types';
|
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||||
import { getTransactionStatusColumn } from 'models/helpers';
|
import {
|
||||||
|
getStockTransferActions,
|
||||||
|
getTransactionStatusColumn,
|
||||||
|
} from 'models/helpers';
|
||||||
import { PurchaseReceiptItem } from './PurchaseReceiptItem';
|
import { PurchaseReceiptItem } from './PurchaseReceiptItem';
|
||||||
import { StockTransfer } from './StockTransfer';
|
import { StockTransfer } from './StockTransfer';
|
||||||
|
import { Fyo } from 'fyo';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
|
||||||
export class PurchaseReceipt extends StockTransfer {
|
export class PurchaseReceipt extends StockTransfer {
|
||||||
items?: PurchaseReceiptItem[];
|
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 { Fyo } from 'fyo';
|
||||||
import { getTransactionStatusColumn } from 'models/helpers';
|
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||||
|
import {
|
||||||
|
getStockTransferActions,
|
||||||
|
getTransactionStatusColumn,
|
||||||
|
} from 'models/helpers';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { ShipmentItem } from './ShipmentItem';
|
import { ShipmentItem } from './ShipmentItem';
|
||||||
import { StockTransfer } from './StockTransfer';
|
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 { Attachment } from 'fyo/core/types';
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import {
|
import {
|
||||||
Action,
|
|
||||||
ChangeArg,
|
ChangeArg,
|
||||||
DefaultMap,
|
DefaultMap,
|
||||||
FiltersMap,
|
FiltersMap,
|
||||||
@ -13,7 +12,7 @@ import { ValidationError } from 'fyo/utils/errors';
|
|||||||
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
||||||
import { Defaults } from 'models/baseModels/Defaults/Defaults';
|
import { Defaults } from 'models/baseModels/Defaults/Defaults';
|
||||||
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
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 { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { TargetField } from 'schemas/types';
|
import { TargetField } from 'schemas/types';
|
||||||
@ -27,6 +26,7 @@ import {
|
|||||||
validateBatch,
|
validateBatch,
|
||||||
validateSerialNumber,
|
validateSerialNumber,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
import { Item } from 'models/baseModels/Item/Item';
|
||||||
|
|
||||||
export abstract class StockTransfer extends Transfer {
|
export abstract class StockTransfer extends Transfer {
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -42,6 +42,13 @@ export abstract class StockTransfer extends Transfer {
|
|||||||
return this.schemaName === ModelNameEnum.Shipment;
|
return this.schemaName === ModelNameEnum.Shipment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get invoiceSchemaName() {
|
||||||
|
if (this.isSales) {
|
||||||
|
return ModelNameEnum.SalesInvoice;
|
||||||
|
}
|
||||||
|
return ModelNameEnum.PurchaseInvoice;
|
||||||
|
}
|
||||||
|
|
||||||
formulas: FormulaMap = {
|
formulas: FormulaMap = {
|
||||||
grandTotal: {
|
grandTotal: {
|
||||||
formula: () => this.getSum('items', 'amount', false),
|
formula: () => this.getSum('items', 'amount', false),
|
||||||
@ -174,10 +181,6 @@ export abstract class StockTransfer extends Transfer {
|
|||||||
await validateSerialNumberStatus(this);
|
await validateSerialNumberStatus(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getActions(fyo: Fyo): Action[] {
|
|
||||||
return [getLedgerLinkAction(fyo, false), getLedgerLinkAction(fyo, true)];
|
|
||||||
}
|
|
||||||
|
|
||||||
async afterSubmit() {
|
async afterSubmit() {
|
||||||
await super.afterSubmit();
|
await super.afterSubmit();
|
||||||
await updateSerialNumbers(this, false);
|
await updateSerialNumbers(this, false);
|
||||||
@ -309,6 +312,68 @@ export abstract class StockTransfer extends Transfer {
|
|||||||
await this.set('date', stDoc.date);
|
await this.set('date', stDoc.date);
|
||||||
await this.set('items', stDoc.items);
|
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) {
|
async function validateSerialNumberStatus(doc: StockTransfer) {
|
||||||
|
@ -174,6 +174,11 @@
|
|||||||
"label": "Attachment",
|
"label": "Attachment",
|
||||||
"fieldtype": "Attachment",
|
"fieldtype": "Attachment",
|
||||||
"section": "References"
|
"section": "References"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abstract": true,
|
||||||
|
"fieldname": "backReference",
|
||||||
|
"section": "References"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keywordFields": ["name", "party"]
|
"keywordFields": ["name", "party"]
|
||||||
|
@ -15,6 +15,13 @@
|
|||||||
"default": "PINV-",
|
"default": "PINV-",
|
||||||
"section": "Default"
|
"section": "Default"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "backReference",
|
||||||
|
"label": "Back Reference",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "PurchaseReceipt",
|
||||||
|
"section": "References"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "items",
|
"fieldname": "items",
|
||||||
"label": "Items",
|
"label": "Items",
|
||||||
|
@ -15,6 +15,13 @@
|
|||||||
"default": "SINV-",
|
"default": "SINV-",
|
||||||
"section": "Default"
|
"section": "Default"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "backReference",
|
||||||
|
"label": "Back Reference",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Shipment",
|
||||||
|
"section": "References"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "items",
|
"fieldname": "items",
|
||||||
"label": "Items",
|
"label": "Items",
|
||||||
|
Loading…
Reference in New Issue
Block a user