Merge branch 'develop' into leave-opening-balance

This commit is contained in:
Rucha Mahabal
2022-03-13 20:30:36 +05:30
committed by GitHub
32 changed files with 316 additions and 98 deletions

View File

@@ -232,7 +232,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
}), transaction.currency, company_account) }), transaction.currency, company_account)
if total_amount > transaction.unallocated_amount: if total_amount > transaction.unallocated_amount:
frappe.throw(_("The Sum Total of Amounts of All Selected Vouchers Should be Less than the Unallocated Amount of the Bank Transaction")) frappe.throw(_("The sum total of amounts of all selected vouchers should be less than the unallocated amount of the bank transaction"))
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
for voucher in vouchers: for voucher in vouchers:

View File

@@ -110,8 +110,13 @@ def get_paid_amount(payment_entry, currency, bank_account):
paid_amount_field = "paid_amount" paid_amount_field = "paid_amount"
if payment_entry.payment_document == 'Payment Entry': if payment_entry.payment_document == 'Payment Entry':
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry) doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
paid_amount_field = ("base_paid_amount"
if doc.paid_to_account_currency == currency else "paid_amount") if doc.payment_type == 'Receive':
paid_amount_field = ("received_amount"
if doc.paid_to_account_currency == currency else "base_received_amount")
elif doc.payment_type == 'Pay':
paid_amount_field = ("paid_amount"
if doc.paid_to_account_currency == currency else "base_paid_amount")
return frappe.db.get_value(payment_entry.payment_document, return frappe.db.get_value(payment_entry.payment_document,
payment_entry.payment_entry, paid_amount_field) payment_entry.payment_entry, paid_amount_field)

View File

@@ -42,7 +42,11 @@ from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timeshe
from erpnext.setup.doctype.company.company import update_company_current_month_sales from erpnext.setup.doctype.company.company import update_company_current_month_sales
from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import (
get_delivery_note_serial_no,
get_serial_nos,
update_serial_nos_after_submit,
)
from erpnext.stock.utils import calculate_mapped_packed_items_return from erpnext.stock.utils import calculate_mapped_packed_items_return
form_grid_templates = { form_grid_templates = {
@@ -226,6 +230,9 @@ class SalesInvoice(SellingController):
# because updating reserved qty in bin depends upon updated delivered qty in SO # because updating reserved qty in bin depends upon updated delivered qty in SO
if self.update_stock == 1: if self.update_stock == 1:
self.update_stock_ledger() self.update_stock_ledger()
if self.is_return and self.update_stock:
update_serial_nos_after_submit(self, "items")
# this sequence because outstanding may get -ve # this sequence because outstanding may get -ve
self.make_gl_entries() self.make_gl_entries()
@@ -1255,14 +1262,14 @@ class SalesInvoice(SellingController):
def update_billing_status_in_dn(self, update_modified=True): def update_billing_status_in_dn(self, update_modified=True):
updated_delivery_notes = [] updated_delivery_notes = []
for d in self.get("items"): for d in self.get("items"):
if d.so_detail: if d.dn_detail:
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
elif d.dn_detail:
billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item` billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
where dn_detail=%s and docstatus=1""", d.dn_detail) where dn_detail=%s and docstatus=1""", d.dn_detail)
billed_amt = billed_amt and billed_amt[0][0] or 0 billed_amt = billed_amt and billed_amt[0][0] or 0
frappe.db.set_value("Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified) frappe.db.set_value("Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified)
updated_delivery_notes.append(d.delivery_note) updated_delivery_notes.append(d.delivery_note)
elif d.so_detail:
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
for dn in set(updated_delivery_notes): for dn in set(updated_delivery_notes):
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified) frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)

View File

@@ -2615,6 +2615,12 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
def test_standalone_serial_no_return(self):
si = create_sales_invoice(item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1)
si.reload()
self.assertTrue(si.items[0].serial_no)
def get_sales_invoice_for_e_invoice(): def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill() si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####' si.naming_series = 'INV-2020-.#####'

View File

@@ -71,8 +71,7 @@ class ShippingRule(Document):
if doc.currency != doc.company_currency: if doc.currency != doc.company_currency:
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2) shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
if shipping_amount: self.add_shipping_rule_to_tax_table(doc, shipping_amount)
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
def get_shipping_amount_from_rules(self, value): def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"): for condition in self.get("conditions"):

View File

@@ -17,8 +17,8 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
def setUp(self) -> None: def setUp(self) -> None:
create_item("Test MR Report Item") create_item("Test MR Report Item")
self.setup_material_request() # to order and receive self.setup_material_request() # to order and receive
self.setup_material_request(order=True) # to receive (ordered) self.setup_material_request(order=True, days=1) # to receive (ordered)
self.setup_material_request(order=True, receive=True) # complete (ordered & received) self.setup_material_request(order=True, receive=True, days=2) # complete (ordered & received)
self.filters = frappe._dict( self.filters = frappe._dict(
company="_Test Company", from_date=today(), to_date=add_days(today(), 30), company="_Test Company", from_date=today(), to_date=add_days(today(), 30),
@@ -32,9 +32,8 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
data = get_data(self.filters) data = get_data(self.filters)
self.assertEqual(len(data), 2) # MRs today should be fetched self.assertEqual(len(data), 2) # MRs today should be fetched
self.filters.from_date = add_days(today(), 1) data = get_data(self.filters.update({"from_date": add_days(today(), 10)}))
data = get_data(self.filters) self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is in future
self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is tomorrow
def test_ordered_received_material_requests(self): def test_ordered_received_material_requests(self):
data = get_data(self.filters) data = get_data(self.filters)
@@ -44,19 +43,19 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
self.assertEqual(data[0].ordered_qty, 0.0) self.assertEqual(data[0].ordered_qty, 0.0)
self.assertEqual(data[1].ordered_qty, 57.0) self.assertEqual(data[1].ordered_qty, 57.0)
def setup_material_request(self, order=False, receive=False): def setup_material_request(self, order=False, receive=False, days=0):
po = None po = None
test_records = frappe.get_test_records('Material Request') test_records = frappe.get_test_records('Material Request')
mr = frappe.copy_doc(test_records[0]) mr = frappe.copy_doc(test_records[0])
mr.transaction_date = today() mr.transaction_date = add_days(today(), days)
mr.schedule_date = add_days(today(), 1) mr.schedule_date = add_days(mr.transaction_date, 1)
for row in mr.items: for row in mr.items:
row.item_code = "Test MR Report Item" row.item_code = "Test MR Report Item"
row.item_name = "Test MR Report Item" row.item_name = "Test MR Report Item"
row.description = "Test MR Report Item" row.description = "Test MR Report Item"
row.uom = "Nos" row.uom = "Nos"
row.schedule_date = add_days(today(), 1) row.schedule_date = mr.schedule_date
mr.submit() mr.submit()
if order or receive: if order or receive:

View File

@@ -208,10 +208,15 @@ def get_already_returned_items(doc):
return items return items
def get_returned_qty_map_for_row(row_name, doctype): def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
child_doctype = doctype + " Item" child_doctype = doctype + " Item"
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype) reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
if doctype in ('Purchase Receipt', 'Purchase Invoice'):
party_type = 'supplier'
else:
party_type = 'customer'
fields = [ fields = [
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype), "sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype) "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
@@ -226,9 +231,12 @@ def get_returned_qty_map_for_row(row_name, doctype):
if doctype == "Purchase Receipt": if doctype == "Purchase Receipt":
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)] fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
# Used retrun against and supplier and is_retrun because there is an index added for it
data = frappe.db.get_list(doctype, data = frappe.db.get_list(doctype,
fields = fields, fields = fields,
filters = [ filters = [
[doctype, "return_against", "=", return_against],
[doctype, party_type, "=", party],
[doctype, "docstatus", "=", 1], [doctype, "docstatus", "=", 1],
[doctype, "is_return", "=", 1], [doctype, "is_return", "=", 1],
[child_doctype, reference_field, "=", row_name] [child_doctype, reference_field, "=", row_name]
@@ -307,7 +315,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.serial_no = '\n'.join(serial_nos) target_doc.serial_no = '\n'.join(serial_nos)
if doctype == "Purchase Receipt": if doctype == "Purchase Receipt":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0)) target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0)) target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
@@ -321,7 +329,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_receipt_item = source_doc.name target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice": elif doctype == "Purchase Invoice":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0)) target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0)) target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
@@ -335,7 +343,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_invoice_item = source_doc.name target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note": elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0)) target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
@@ -348,7 +356,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice" or doctype == "POS Invoice": elif doctype == "Sales Invoice" or doctype == "POS Invoice":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0)) target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))

View File

@@ -37,6 +37,8 @@ class calculate_taxes_and_totals(object):
self.set_discount_amount() self.set_discount_amount()
self.apply_discount_amount() self.apply_discount_amount()
self.calculate_shipping_charges()
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]: if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
self.calculate_total_advance() self.calculate_total_advance()
@@ -50,7 +52,6 @@ class calculate_taxes_and_totals(object):
self.initialize_taxes() self.initialize_taxes()
self.determine_exclusive_rate() self.determine_exclusive_rate()
self.calculate_net_total() self.calculate_net_total()
self.calculate_shipping_charges()
self.calculate_taxes() self.calculate_taxes()
self.manipulate_grand_total_for_inclusive_tax() self.manipulate_grand_total_for_inclusive_tax()
self.calculate_totals() self.calculate_totals()
@@ -276,6 +277,8 @@ class calculate_taxes_and_totals(object):
shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule) shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
shipping_rule.apply(self.doc) shipping_rule.apply(self.doc)
self._calculate()
def calculate_taxes(self): def calculate_taxes(self):
rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment') rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
if not rounding_adjustment_computed: if not rounding_adjustment_computed:

View File

@@ -214,6 +214,7 @@ class Lead(SellingController):
}) })
contact.insert(ignore_permissions=True) contact.insert(ignore_permissions=True)
contact.reload() # load changes by hooks on contact
return contact return contact

View File

@@ -32,6 +32,8 @@
"monthly_repayment_amount", "monthly_repayment_amount",
"repayment_start_date", "repayment_start_date",
"is_term_loan", "is_term_loan",
"accounting_dimensions_section",
"cost_center",
"account_info", "account_info",
"mode_of_payment", "mode_of_payment",
"disbursement_account", "disbursement_account",
@@ -366,12 +368,23 @@
"options": "Account", "options": "Account",
"read_only": 1, "read_only": 1,
"reqd": 1 "reqd": 1
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-01-25 16:29:16.325501", "modified": "2022-03-10 11:50:31.957360",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan", "name": "Loan",

View File

@@ -25,6 +25,7 @@ class Loan(AccountsController):
self.set_loan_amount() self.set_loan_amount()
self.validate_loan_amount() self.validate_loan_amount()
self.set_missing_fields() self.set_missing_fields()
self.validate_cost_center()
self.validate_accounts() self.validate_accounts()
self.check_sanctioned_amount_limit() self.check_sanctioned_amount_limit()
self.validate_repay_from_salary() self.validate_repay_from_salary()
@@ -45,6 +46,13 @@ class Loan(AccountsController):
frappe.throw(_("Account {0} does not belongs to company {1}").format(frappe.bold(self.get(fieldname)), frappe.throw(_("Account {0} does not belongs to company {1}").format(frappe.bold(self.get(fieldname)),
frappe.bold(self.company))) frappe.bold(self.company)))
def validate_cost_center(self):
if not self.cost_center and self.rate_of_interest != 0:
self.cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
if not self.cost_center:
frappe.throw(_('Cost center is mandatory for loans having rate of interest greater than 0'))
def on_submit(self): def on_submit(self):
self.link_loan_security_pledge() self.link_loan_security_pledge()

View File

@@ -6,7 +6,6 @@ import frappe
from frappe import _ from frappe import _
from frappe.utils import add_days, cint, date_diff, flt, get_datetime, getdate, nowdate from frappe.utils import add_days, cint, date_diff, flt, get_datetime, getdate, nowdate
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@@ -41,6 +40,8 @@ class LoanInterestAccrual(AccountsController):
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = [] gle_map = []
cost_center = frappe.db.get_value('Loan', self.loan, 'cost_center')
if self.interest_amount: if self.interest_amount:
gle_map.append( gle_map.append(
self.get_gl_dict({ self.get_gl_dict({
@@ -54,7 +55,7 @@ class LoanInterestAccrual(AccountsController):
"against_voucher": self.loan, "against_voucher": self.loan,
"remarks": _("Interest accrued from {0} to {1} against loan: {2}").format( "remarks": _("Interest accrued from {0} to {1} against loan: {2}").format(
self.last_accrual_date, self.posting_date, self.loan), self.last_accrual_date, self.posting_date, self.loan),
"cost_center": erpnext.get_default_cost_center(self.company), "cost_center": cost_center,
"posting_date": self.posting_date "posting_date": self.posting_date
}) })
) )
@@ -69,7 +70,7 @@ class LoanInterestAccrual(AccountsController):
"against_voucher": self.loan, "against_voucher": self.loan,
"remarks": ("Interest accrued from {0} to {1} against loan: {2}").format( "remarks": ("Interest accrued from {0} to {1} against loan: {2}").format(
self.last_accrual_date, self.posting_date, self.loan), self.last_accrual_date, self.posting_date, self.loan),
"cost_center": erpnext.get_default_cost_center(self.company), "cost_center": cost_center,
"posting_date": self.posting_date "posting_date": self.posting_date
}) })
) )

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import functools import functools
import re
from collections import deque from collections import deque
from operator import itemgetter from operator import itemgetter
from typing import List from typing import List
@@ -103,25 +104,33 @@ class BOM(WebsiteGenerator):
) )
def autoname(self): def autoname(self):
names = frappe.db.sql_list("""select name from `tabBOM` where item=%s""", self.item) # ignore amended documents while calculating current index
existing_boms = frappe.get_all(
"BOM",
filters={"item": self.item, "amended_from": ["is", "not set"]},
pluck="name"
)
if names: if existing_boms:
# name can be BOM/ITEM/001, BOM/ITEM/001-1, BOM-ITEM-001, BOM-ITEM-001-1 index = self.get_next_version_index(existing_boms)
# split by item
names = [name.split(self.item, 1) for name in names]
names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)]
# split by (-) if cancelled
if names:
names = [cint(name.split('-')[-1]) for name in names]
idx = max(names) + 1
else:
idx = 1
else: else:
idx = 1 index = 1
prefix = self.doctype
suffix = "%.3i" % index # convert index to string (1 -> "001")
bom_name = f"{prefix}-{self.item}-{suffix}"
if len(bom_name) <= 140:
name = bom_name
else:
# since max characters for name is 140, remove enough characters from the
# item name to fit the prefix, suffix and the separators
truncated_length = 140 - (len(prefix) + len(suffix) + 2)
truncated_item_name = self.item[:truncated_length]
# if a partial word is found after truncate, remove the extra characters
truncated_item_name = truncated_item_name.rsplit(" ", 1)[0]
name = f"{prefix}-{truncated_item_name}-{suffix}"
name = 'BOM-' + self.item + ('-%.3i' % idx)
if frappe.db.exists("BOM", name): if frappe.db.exists("BOM", name):
conflicting_bom = frappe.get_doc("BOM", name) conflicting_bom = frappe.get_doc("BOM", name)
@@ -134,6 +143,26 @@ class BOM(WebsiteGenerator):
self.name = name self.name = name
@staticmethod
def get_next_version_index(existing_boms: List[str]) -> int:
# split by "/" and "-"
delimiters = ["/", "-"]
pattern = "|".join(map(re.escape, delimiters))
bom_parts = [re.split(pattern, bom_name) for bom_name in existing_boms]
# filter out BOMs that do not follow the following formats: BOM/ITEM/001, BOM-ITEM-001
valid_bom_parts = list(filter(lambda x: len(x) > 1 and x[-1], bom_parts))
# extract the current index from the BOM parts
if valid_bom_parts:
# handle cancelled and submitted documents
indexes = [cint(part[-1]) for part in valid_bom_parts]
index = max(indexes) + 1
else:
index = 1
return index
def validate(self): def validate(self):
self.route = frappe.scrub(self.name).replace('_', '-') self.route = frappe.scrub(self.name).replace('_', '-')
@@ -192,12 +221,13 @@ class BOM(WebsiteGenerator):
if self.routing: if self.routing:
self.set("operations", []) self.set("operations", [])
fields = ["sequence_id", "operation", "workstation", "description", fields = ["sequence_id", "operation", "workstation", "description",
"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate"] "time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate",
"set_cost_based_on_bom_qty", "fixed_time"]
for row in frappe.get_all("BOM Operation", fields = fields, for row in frappe.get_all("BOM Operation", fields = fields,
filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"): filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
child = self.append('operations', row) child = self.append('operations', row)
child.hour_rate = flt(row.hour_rate / self.conversion_rate, 2) child.hour_rate = flt(row.hour_rate / self.conversion_rate, child.precision("hour_rate"))
def set_bom_material_details(self): def set_bom_material_details(self):
for item in self.get("items"): for item in self.get("items"):

View File

@@ -432,6 +432,69 @@ class TestBOM(FrappeTestCase):
self.assertEqual(bom.transfer_material_against, "Work Order") self.assertEqual(bom.transfer_material_against, "Work Order")
bom.delete() bom.delete()
def test_bom_name_length(self):
""" test >140 char names"""
bom_tree = {
"x" * 140 : {
" ".join(["abc"] * 35): {}
}
}
create_nested_bom(bom_tree, prefix="")
def test_version_index(self):
bom = frappe.new_doc("BOM")
version_index_test_cases = [
(1, []),
(1, ["BOM#XYZ"]),
(2, ["BOM/ITEM/001"]),
(2, ["BOM-ITEM-001"]),
(3, ["BOM-ITEM-001", "BOM-ITEM-002"]),
(4, ["BOM-ITEM-001", "BOM-ITEM-002", "BOM-ITEM-003"]),
]
for expected_index, existing_boms in version_index_test_cases:
with self.subTest():
self.assertEqual(expected_index, bom.get_next_version_index(existing_boms),
msg=f"Incorrect index for {existing_boms}")
def test_bom_versioning(self):
bom_tree = {
frappe.generate_hash(length=10) : {
frappe.generate_hash(length=10): {}
}
}
bom = create_nested_bom(bom_tree, prefix="")
self.assertEqual(int(bom.name.split("-")[-1]), 1)
original_bom_name = bom.name
bom.cancel()
bom.reload()
self.assertEqual(bom.name, original_bom_name)
# create a new amendment
amendment = frappe.copy_doc(bom)
amendment.docstatus = 0
amendment.amended_from = bom.name
amendment.save()
amendment.submit()
amendment.reload()
self.assertNotEqual(amendment.name, bom.name)
# `origname-001-1` version
self.assertEqual(int(amendment.name.split("-")[-1]), 1)
self.assertEqual(int(amendment.name.split("-")[-2]), 1)
# create a new version
version = frappe.copy_doc(amendment)
version.docstatus = 0
version.amended_from = None
version.save()
self.assertNotEqual(amendment.name, version.name)
self.assertEqual(int(version.name.split("-")[-1]), 2)
def get_default_bom(item_code="_Test FG Item 2"): def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@@ -66,7 +66,8 @@
"label": "Hour Rate", "label": "Hour Rate",
"oldfieldname": "hour_rate", "oldfieldname": "hour_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "currency" "options": "currency",
"precision": "2"
}, },
{ {
"description": "In minutes", "description": "In minutes",
@@ -186,7 +187,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-12-15 03:00:00.473173", "modified": "2022-03-10 06:19:08.462027",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Operation", "name": "BOM Operation",

View File

@@ -358,3 +358,4 @@ erpnext.patches.v13_0.update_accounts_in_loan_docs
erpnext.patches.v14_0.update_batch_valuation_flag erpnext.patches.v14_0.update_batch_valuation_flag
erpnext.patches.v14_0.delete_non_profit_doctypes erpnext.patches.v14_0.delete_non_profit_doctypes
erpnext.patches.v14_0.update_employee_advance_status erpnext.patches.v14_0.update_employee_advance_status
erpnext.patches.v13_0.add_cost_center_in_loans

View File

@@ -0,0 +1,16 @@
import frappe
def execute():
frappe.reload_doc('loan_management', 'doctype', 'loan')
loan = frappe.qb.DocType('Loan')
for company in frappe.get_all('Company', pluck='name'):
default_cost_center = frappe.db.get_value('Company', company, 'cost_center')
frappe.qb.update(
loan
).set(
loan.cost_center, default_cost_center
).where(
loan.company == company
).run()

View File

@@ -39,6 +39,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this._calculate_taxes_and_totals(); this._calculate_taxes_and_totals();
this.calculate_discount_amount(); this.calculate_discount_amount();
this.calculate_shipping_charges();
// Advance calculation applicable to Sales /Purchase Invoice // Advance calculation applicable to Sales /Purchase Invoice
if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype) if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
&& this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) { && this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) {
@@ -81,7 +83,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.initialize_taxes(); this.initialize_taxes();
this.determine_exclusive_rate(); this.determine_exclusive_rate();
this.calculate_net_total(); this.calculate_net_total();
this.calculate_shipping_charges();
this.calculate_taxes(); this.calculate_taxes();
this.manipulate_grand_total_for_inclusive_tax(); this.manipulate_grand_total_for_inclusive_tax();
this.calculate_totals(); this.calculate_totals();
@@ -275,6 +276,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) { if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) {
this.shipping_rule(); this.shipping_rule();
this._calculate_taxes_and_totals();
} }
} }

View File

@@ -127,7 +127,8 @@ class GSTR3BReport(Document):
def get_inward_nil_exempt(self, state): def get_inward_nil_exempt(self, state):
inward_nil_exempt = frappe.db.sql(""" inward_nil_exempt = frappe.db.sql("""
SELECT p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst SELECT p.place_of_supply, p.supplier_address,
i.base_amount, i.is_nil_exempt, i.is_non_gst
FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
WHERE p.docstatus = 1 and p.name = i.parent WHERE p.docstatus = 1 and p.name = i.parent
and p.is_opening = 'No' and p.is_opening = 'No'
@@ -135,7 +136,7 @@ class GSTR3BReport(Document):
and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and
month(p.posting_date) = %s and year(p.posting_date) = %s month(p.posting_date) = %s and year(p.posting_date) = %s
and p.company = %s and p.company_gstin = %s and p.company = %s and p.company_gstin = %s
GROUP BY p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", """,
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
inward_nil_exempt_details = { inward_nil_exempt_details = {
@@ -149,18 +150,24 @@ class GSTR3BReport(Document):
} }
} }
address_state_map = get_address_state_map()
for d in inward_nil_exempt: for d in inward_nil_exempt:
if d.place_of_supply: if not d.place_of_supply:
if (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \ d.place_of_supply = "00-" + cstr(state)
and state == d.place_of_supply.split("-")[1]:
inward_nil_exempt_details["gst"]["intra"] += d.base_amount supplier_state = address_state_map.get(d.supplier_address) or state
elif (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
and state != d.place_of_supply.split("-")[1]: if (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
inward_nil_exempt_details["gst"]["inter"] += d.base_amount and cstr(supplier_state) == cstr(d.place_of_supply.split("-")[1]):
elif d.is_non_gst == 1 and state == d.place_of_supply.split("-")[1]: inward_nil_exempt_details["gst"]["intra"] += d.base_amount
inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount elif (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
elif d.is_non_gst == 1 and state != d.place_of_supply.split("-")[1]: and cstr(supplier_state) != cstr(d.place_of_supply.split("-")[1]):
inward_nil_exempt_details["non_gst"]["inter"] += d.base_amount inward_nil_exempt_details["gst"]["inter"] += d.base_amount
elif d.is_non_gst == 1 and cstr(supplier_state) == cstr(d.place_of_supply.split("-")[1]):
inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount
elif d.is_non_gst == 1 and cstr(supplier_state) != cstr(d.place_of_supply.split("-")[1]):
inward_nil_exempt_details["non_gst"]["inter"] += d.base_amount
return inward_nil_exempt_details return inward_nil_exempt_details
@@ -419,6 +426,11 @@ class GSTR3BReport(Document):
return ",".join(missing_field_invoices) return ",".join(missing_field_invoices)
def get_address_state_map():
return frappe._dict(
frappe.get_all('Address', fields=['name', 'gst_state'], as_list=1)
)
def get_json(template): def get_json(template):
file_path = os.path.join(os.path.dirname(__file__), '{template}.json'.format(template=template)) file_path = os.path.join(os.path.dirname(__file__), '{template}.json'.format(template=template))
with open(file_path, 'r') as f: with open(file_path, 'r') as f:

View File

@@ -1477,6 +1477,28 @@ class TestSalesOrder(FrappeTestCase):
self.assertEqual(so.items[0].work_order_qty, wo.produced_qty) self.assertEqual(so.items[0].work_order_qty, wo.produced_qty)
self.assertEqual(mr.status, "Manufactured") self.assertEqual(mr.status, "Manufactured")
def test_sales_order_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test")
sales_order = make_sales_order(do_not_save=True)
sales_order.shipping_rule = shipping_rule.name
sales_order.items[0].qty = 1
sales_order.save()
self.assertEqual(sales_order.taxes[0].tax_amount, 50)
sales_order.items[0].qty = 2
sales_order.save()
self.assertEqual(sales_order.taxes[0].tax_amount, 100)
sales_order.items[0].qty = 3
sales_order.save()
self.assertEqual(sales_order.taxes[0].tax_amount, 200)
sales_order.items[0].qty = 21
sales_order.save()
self.assertEqual(sales_order.taxes[0].tax_amount, 0)
def automatically_fetch_payment_terms(enable=1): def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.automatically_fetch_payment_terms = enable accounts_settings.automatically_fetch_payment_terms = enable

View File

@@ -1315,7 +1315,7 @@
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-10-09 14:29:13.428984", "modified": "2022-03-10 14:29:13.428984",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@@ -13,7 +13,10 @@ from frappe.utils import cint, flt
from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no from erpnext.stock.doctype.serial_no.serial_no import (
get_delivery_note_serial_no,
update_serial_nos_after_submit,
)
from erpnext.stock.utils import calculate_mapped_packed_items_return from erpnext.stock.utils import calculate_mapped_packed_items_return
form_grid_templates = { form_grid_templates = {
@@ -220,6 +223,9 @@ class DeliveryNote(SellingController):
# Updating stock ledger should always be called after updating prevdoc status, # Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO # because updating reserved qty in bin depends upon updated delivered qty in SO
self.update_stock_ledger() self.update_stock_ledger()
if self.is_return:
update_serial_nos_after_submit(self, "items")
self.make_gl_entries() self.make_gl_entries()
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
@@ -342,25 +348,21 @@ def update_billed_amount_based_on_so(so_detail, update_modified=True):
from frappe.query_builder.functions import Sum from frappe.query_builder.functions import Sum
# Billed against Sales Order directly # Billed against Sales Order directly
si = frappe.qb.DocType("Sales Invoice").as_("si")
si_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item") si_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item")
sum_amount = Sum(si_item.amount).as_("amount") sum_amount = Sum(si_item.amount).as_("amount")
billed_against_so = frappe.qb.from_(si).from_(si_item).select(sum_amount).where( billed_against_so = frappe.qb.from_(si_item).select(sum_amount).where(
(si_item.parent == si.name) &
(si_item.so_detail == so_detail) & (si_item.so_detail == so_detail) &
((si_item.dn_detail.isnull()) | (si_item.dn_detail == '')) & ((si_item.dn_detail.isnull()) | (si_item.dn_detail == '')) &
(si_item.docstatus == 1) & (si_item.docstatus == 1)
(si.update_stock == 0)
).run() ).run()
billed_against_so = billed_against_so and billed_against_so[0][0] or 0 billed_against_so = billed_against_so and billed_against_so[0][0] or 0
# Get all Delivery Note Item rows against the Sales Order Item row # Get all Delivery Note Item rows against the Sales Order Item row
dn = frappe.qb.DocType("Delivery Note").as_("dn") dn = frappe.qb.DocType("Delivery Note").as_("dn")
dn_item = frappe.qb.DocType("Delivery Note Item").as_("dn_item") dn_item = frappe.qb.DocType("Delivery Note Item").as_("dn_item")
dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent, dn_item.stock_qty, dn_item.returned_qty).where( dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent).where(
(dn.name == dn_item.parent) & (dn.name == dn_item.parent) &
(dn_item.so_detail == so_detail) & (dn_item.so_detail == so_detail) &
(dn.docstatus == 1) & (dn.docstatus == 1) &
@@ -385,11 +387,7 @@ def update_billed_amount_based_on_so(so_detail, update_modified=True):
# Distribute billed amount directly against SO between DNs based on FIFO # Distribute billed amount directly against SO between DNs based on FIFO
if billed_against_so and billed_amt_agianst_dn < dnd.amount: if billed_against_so and billed_amt_agianst_dn < dnd.amount:
if dnd.returned_qty: pending_to_bill = flt(dnd.amount) - billed_amt_agianst_dn
pending_to_bill = flt(dnd.amount) * (dnd.stock_qty - dnd.returned_qty) / dnd.stock_qty
else:
pending_to_bill = flt(dnd.amount)
pending_to_bill -= billed_amt_agianst_dn
if pending_to_bill <= billed_against_so: if pending_to_bill <= billed_against_so:
billed_amt_agianst_dn += pending_to_bill billed_amt_agianst_dn += pending_to_bill
billed_against_so -= pending_to_bill billed_against_so -= pending_to_bill
@@ -798,3 +796,6 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist return doclist
def on_doctype_update():
frappe.db.add_index("Delivery Note", ["customer", "is_return", "return_against"])

View File

@@ -822,6 +822,11 @@ class TestDeliveryNote(FrappeTestCase):
automatically_fetch_payment_terms(enable=0) automatically_fetch_payment_terms(enable=0)
def test_standalone_serial_no_return(self):
dn = create_delivery_note(item_code="_Test Serialized Item With Series", is_return=True, qty=-1)
dn.reload()
self.assertTrue(dn.items[0].serial_no)
def create_return_delivery_note(**args): def create_return_delivery_note(**args):
args = frappe._dict(args) args = frappe._dict(args)
from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.controllers.sales_and_purchase_return import make_return_doc

View File

@@ -392,6 +392,7 @@ class Item(Document):
self.validate_properties_before_merge(new_name) self.validate_properties_before_merge(new_name)
self.validate_duplicate_product_bundles_before_merge(old_name, new_name) self.validate_duplicate_product_bundles_before_merge(old_name, new_name)
self.validate_duplicate_website_item_before_merge(old_name, new_name) self.validate_duplicate_website_item_before_merge(old_name, new_name)
self.delete_old_bins(old_name)
def after_rename(self, old_name, new_name, merge): def after_rename(self, old_name, new_name, merge):
if merge: if merge:
@@ -420,6 +421,9 @@ class Item(Document):
frappe.db.set_value(dt, d.name, "item_wise_tax_detail", frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
json.dumps(item_wise_tax_detail), update_modified=False) json.dumps(item_wise_tax_detail), update_modified=False)
def delete_old_bins(self, old_name):
frappe.db.delete("Bin", {"item_code": old_name})
def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name): def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name):
records = frappe.db.sql(""" SELECT parent, COUNT(*) as records records = frappe.db.sql(""" SELECT parent, COUNT(*) as records
FROM `tabStock Reconciliation Item` FROM `tabStock Reconciliation Item`
@@ -500,11 +504,11 @@ class Item(Document):
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
repost_stock_for_warehouses = frappe.db.sql_list("""select distinct warehouse repost_stock_for_warehouses = frappe.get_all("Stock Ledger Entry",
from tabBin where item_code=%s""", new_name) "warehouse", filters={"item_code": new_name}, pluck="warehouse", distinct=True)
# Delete all existing bins to avoid duplicate bins for the same item and warehouse # Delete all existing bins to avoid duplicate bins for the same item and warehouse
frappe.db.sql("delete from `tabBin` where item_code=%s", new_name) frappe.db.delete("Bin", {"item_code": new_name})
for warehouse in repost_stock_for_warehouses: for warehouse in repost_stock_for_warehouses:
repost_stock(new_name, warehouse) repost_stock(new_name, warehouse)

View File

@@ -371,23 +371,24 @@ class TestItem(FrappeTestCase):
variant.save() variant.save()
def test_item_merging(self): def test_item_merging(self):
create_item("Test Item for Merging 1") old = create_item(frappe.generate_hash(length=20)).name
create_item("Test Item for Merging 2") new = create_item(frappe.generate_hash(length=20)).name
make_stock_entry(item_code="Test Item for Merging 1", target="_Test Warehouse - _TC", make_stock_entry(item_code=old, target="_Test Warehouse - _TC",
qty=1, rate=100) qty=1, rate=100)
make_stock_entry(item_code="Test Item for Merging 2", target="_Test Warehouse 1 - _TC", make_stock_entry(item_code=old, target="_Test Warehouse 1 - _TC",
qty=1, rate=100)
make_stock_entry(item_code=new, target="_Test Warehouse 1 - _TC",
qty=1, rate=100) qty=1, rate=100)
frappe.rename_doc("Item", "Test Item for Merging 1", "Test Item for Merging 2", merge=True) frappe.rename_doc("Item", old, new, merge=True)
self.assertFalse(frappe.db.exists("Item", "Test Item for Merging 1")) self.assertFalse(frappe.db.exists("Item", old))
self.assertTrue(frappe.db.get_value("Bin", self.assertTrue(frappe.db.get_value("Bin",
{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse - _TC"})) {"item_code": new, "warehouse": "_Test Warehouse - _TC"}))
self.assertTrue(frappe.db.get_value("Bin", self.assertTrue(frappe.db.get_value("Bin",
{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"})) {"item_code": new, "warehouse": "_Test Warehouse 1 - _TC"}))
def test_item_merging_with_product_bundle(self): def test_item_merging_with_product_bundle(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle

View File

@@ -214,6 +214,7 @@ frappe.ui.form.on('Material Request', {
material_request_type: frm.doc.material_request_type, material_request_type: frm.doc.material_request_type,
plc_conversion_rate: 1, plc_conversion_rate: 1,
rate: item.rate, rate: item.rate,
uom: item.uom,
conversion_factor: item.conversion_factor conversion_factor: item.conversion_factor
}, },
overwrite_warehouse: overwrite_warehouse overwrite_warehouse: overwrite_warehouse
@@ -392,6 +393,7 @@ frappe.ui.form.on("Material Request Item", {
item_code: function(frm, doctype, name) { item_code: function(frm, doctype, name) {
const item = locals[doctype][name]; const item = locals[doctype][name];
item.rate = 0; item.rate = 0;
item.uom = '';
set_schedule_date(frm); set_schedule_date(frm);
frm.events.get_item_data(frm, item, true); frm.events.get_item_data(frm, item, true);
}, },

View File

@@ -1165,7 +1165,7 @@
"idx": 261, "idx": 261,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-02-01 11:40:52.690984", "modified": "2022-03-10 11:40:52.690984",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt", "name": "Purchase Receipt",

View File

@@ -926,3 +926,6 @@ def get_item_account_wise_additional_cost(purchase_document):
account.base_amount * item.get(based_on_field) / total_item_cost account.base_amount * item.get(based_on_field) / total_item_cost
return item_account_wise_cost return item_account_wise_cost
def on_doctype_update():
frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"])

View File

@@ -769,8 +769,7 @@ class TestPurchaseReceipt(FrappeTestCase):
update_purchase_receipt_status, update_purchase_receipt_status,
) )
pr = make_purchase_receipt(do_not_submit=True) pr = make_purchase_receipt()
pr.submit()
update_purchase_receipt_status(pr.name, "Closed") update_purchase_receipt_status(pr.name, "Closed")
self.assertEqual( self.assertEqual(

View File

@@ -118,7 +118,8 @@ def repost(doc):
doc.set_status('Failed') doc.set_status('Failed')
raise raise
finally: finally:
frappe.db.commit() if not frappe.flags.in_test:
frappe.db.commit()
def repost_sl_entries(doc): def repost_sl_entries(doc):
if doc.based_on == 'Transaction': if doc.based_on == 'Transaction':

View File

@@ -394,7 +394,7 @@ def update_serial_nos(sle, item_det):
if not sle.is_cancelled and not sle.serial_no and cint(sle.actual_qty) > 0 \ if not sle.is_cancelled and not sle.serial_no and cint(sle.actual_qty) > 0 \
and item_det.has_serial_no == 1 and item_det.serial_no_series: and item_det.has_serial_no == 1 and item_det.serial_no_series:
serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty)
frappe.db.set(sle, "serial_no", serial_nos) sle.db_set("serial_no", serial_nos)
validate_serial_no(sle, item_det) validate_serial_no(sle, item_det)
if sle.serial_no: if sle.serial_no:
auto_make_serial_nos(sle) auto_make_serial_nos(sle)
@@ -516,13 +516,16 @@ def update_serial_nos_after_submit(controller, parentfield):
if controller.doctype == "Stock Entry": if controller.doctype == "Stock Entry":
warehouse = d.t_warehouse warehouse = d.t_warehouse
qty = d.transfer_qty qty = d.transfer_qty
elif controller.doctype in ("Sales Invoice", "Delivery Note"):
warehouse = d.warehouse
qty = d.stock_qty
else: else:
warehouse = d.warehouse warehouse = d.warehouse
qty = (d.qty if controller.doctype == "Stock Reconciliation" qty = (d.qty if controller.doctype == "Stock Reconciliation"
else d.stock_qty) else d.stock_qty)
for sle in stock_ledger_entries: for sle in stock_ledger_entries:
if sle.voucher_detail_no==d.name: if sle.voucher_detail_no==d.name:
if not accepted_serial_nos_updated and qty and abs(sle.actual_qty)==qty \ if not accepted_serial_nos_updated and qty and abs(sle.actual_qty) == abs(qty) \
and sle.warehouse == warehouse and sle.serial_no != d.serial_no: and sle.warehouse == warehouse and sle.serial_no != d.serial_no:
d.serial_no = sle.serial_no d.serial_no = sle.serial_no
frappe.db.set_value(d.doctype, d.name, "serial_no", sle.serial_no) frappe.db.set_value(d.doctype, d.name, "serial_no", sle.serial_no)

View File

@@ -838,11 +838,13 @@ class update_entries_after(object):
for warehouse, data in self.data.items(): for warehouse, data in self.data.items():
bin_name = get_or_make_bin(self.item_code, warehouse) bin_name = get_or_make_bin(self.item_code, warehouse)
frappe.db.set_value('Bin', bin_name, { updated_values = {
"valuation_rate": data.valuation_rate,
"actual_qty": data.qty_after_transaction, "actual_qty": data.qty_after_transaction,
"stock_value": data.stock_value "stock_value": data.stock_value
}) }
if data.valuation_rate is not None:
updated_values["valuation_rate"] = data.valuation_rate
frappe.db.set_value('Bin', bin_name, updated_values)
def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):