2
0
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:
Alan 2023-06-04 23:29:27 -07:00 committed by GitHub
commit 081ad7a38f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 184 additions and 12 deletions

View File

@ -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,
});
}

View File

@ -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'],
},

View File

@ -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`,

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -174,6 +174,11 @@
"label": "Attachment",
"fieldtype": "Attachment",
"section": "References"
},
{
"abstract": true,
"fieldname": "backReference",
"section": "References"
}
],
"keywordFields": ["name", "party"]

View File

@ -15,6 +15,13 @@
"default": "PINV-",
"section": "Default"
},
{
"fieldname": "backReference",
"label": "Back Reference",
"fieldtype": "Link",
"target": "PurchaseReceipt",
"section": "References"
},
{
"fieldname": "items",
"label": "Items",

View File

@ -15,6 +15,13 @@
"default": "SINV-",
"section": "Default"
},
{
"fieldname": "backReference",
"label": "Back Reference",
"fieldtype": "Link",
"target": "Shipment",
"section": "References"
},
{
"fieldname": "items",
"label": "Items",