Skip to content

Commit e2d9d1c

Browse files
authored
Merge pull request #291 from BrainWise-DEV/PN-60-pay-on-account
feat: add support for credit sales in invoice submission process
2 parents 8aab8ab + 9f624c2 commit e2d9d1c

5 files changed

Lines changed: 43 additions & 9 deletions

File tree

POS/src/composables/useInvoice.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ export function useInvoice() {
974974
targetDoctype = "Sales Invoice",
975975
deliveryDate = null,
976976
writeOffAmount = 0,
977+
isCreditSale = false,
977978
) {
978979
/**
979980
* Two-step submission process with mutex protection:
@@ -1065,6 +1066,8 @@ export function useInvoice() {
10651066
if (redeemedCustomerCredit > 0 && customerCreditDict.length > 0) {
10661067
submitData.redeemed_customer_credit = redeemedCustomerCredit
10671068
submitData.customer_credit_dict = customerCreditDict
1069+
if (isCreditSale && invoicePayments.length === 0) {
1070+
submitData.is_credit_sale = 1
10681071
}
10691072

10701073
try {

POS/src/pages/POSSale.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,7 @@ async function handlePaymentCompleted(paymentData) {
20582058
total_discount: cartStore.totalDiscount,
20592059
write_off_amount: paymentData.write_off_amount || 0,
20602060
change_amount: paymentData.change_amount || 0,
2061+
is_credit_sale: paymentData.is_credit_sale ? 1 : 0,
20612062
edited_from: editingOfflineContext?.originalOfflineId || null,
20622063
};
20632064
@@ -2148,7 +2149,9 @@ async function handlePaymentCompleted(paymentData) {
21482149
// Get item codes from cart before clearing
21492150
const soldItemCodes = cartStore.invoiceItems.map((item) => item.item_code);
21502151
2151-
const result = await cartStore.submitInvoice();
2152+
const result = await cartStore.submitInvoice({
2153+
isCreditSale: Boolean(paymentData.is_credit_sale),
2154+
});
21522155
21532156
if (result) {
21542157
uiStore.clearLastOfflinePrintDoc();

POS/src/stores/posCart.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ export const usePOSCartStore = defineStore("posCart", () => {
254254
writeOffAmount.value = amount || 0
255255
}
256256

257-
async function submitInvoice() {
257+
async function submitInvoice(options = {}) {
258258
if (invoiceItems.value.length === 0) {
259259
showWarning(__("Cart is empty"))
260260
return
@@ -264,7 +264,12 @@ export const usePOSCartStore = defineStore("posCart", () => {
264264
return
265265
}
266266

267-
const result = await baseSubmitInvoice(targetDoctype.value, deliveryDate.value, writeOffAmount.value)
267+
const result = await baseSubmitInvoice(
268+
targetDoctype.value,
269+
deliveryDate.value,
270+
writeOffAmount.value,
271+
Boolean(options.isCreditSale),
272+
)
268273
// Reset write-off amount after successful submission
269274
if (result) {
270275
writeOffAmount.value = 0

pos_next/api/invoices.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,22 @@ def submit_invoice(invoice=None, data=None):
14141414
if redeemed_customer_credit and not invoice_doc.payments:
14151415
invoice_doc.flags.pos_next_redeemed_customer_credit = flt(redeemed_customer_credit)
14161416

1417+
.
1418+
is_credit_sale = cint(data.get("is_credit_sale") or invoice.get("is_credit_sale"))
1419+
if (
1420+
is_credit_sale
1421+
and not invoice_doc.payments
1422+
and flt(invoice_doc.grand_total) > 0
1423+
):
1424+
allow_credit_sale = cint(
1425+
frappe.db.get_value(
1426+
DOCTYPE_POS_SETTINGS, {"pos_profile": pos_profile}, "allow_credit_sale"
1427+
)
1428+
)
1429+
if not allow_credit_sale:
1430+
frappe.throw(_("Credit sales are not enabled for this POS Profile."))
1431+
invoice_doc.flags.pos_next_credit_sale = 1
1432+
14171433
# Save before submit
14181434
invoice_doc.flags.ignore_permissions = True
14191435
frappe.flags.ignore_account_permission = True

pos_next/overrides/sales_invoice.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,21 @@ def make_pos_gl_entries(self, gl_entries):
173173

174174
def validate_pos_paid_amount(self):
175175
"""
176-
Allow pure customer-credit POS sales to submit without a payment row.
176+
Allow POS sales to submit without a payment row in two cases:
177177
178-
POSNext redeems customer credit after submit through Journal Entries /
179-
Payment Entry allocation, so there is no real Mode of Payment row to send.
180-
Only bypass the core POS payment-row check when submit_invoice has explicitly
181-
marked the document for customer-credit redemption.
178+
1. Pure customer-credit redemption — POSNext redeems existing customer
179+
credit after submit through Journal Entries / Payment Entry allocation,
180+
so there is no real Mode of Payment row to send.
181+
2. "Pay on Account" credit sales — the cashier intentionally puts the full
182+
amount on the customer's account, leaving the invoice outstanding.
183+
184+
Both are only honoured when submit_invoice has explicitly marked the
185+
document via the corresponding flag (set after verifying the POS Settings
186+
permit the operation), so a tampered client can't bypass the check.
182187
"""
183-
if getattr(self.flags, "pos_next_redeemed_customer_credit", 0):
188+
if getattr(self.flags, "pos_next_redeemed_customer_credit", 0) or getattr(
189+
self.flags, "pos_next_credit_sale", 0
190+
):
184191
if len(self.payments) == 0 and cint(self.is_pos) and flt(self.grand_total) > 0:
185192
return
186193

0 commit comments

Comments
 (0)