fix: optimize reposting of gle and sle (#24702)

* fix(india): escape for special characters in JSON (#24695)

JSON does not accept special whitespace characters like tab, carriage
return, line feed

Ref: https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf

Related issue: ISS-20-21-09811

* fix: Accounting Dimension creation background job timeout

* fix(regional): vehicle no is mandatory for ewaybill generation (#24679)

* fix: vehicle no required for e-invoice

* fix: ewaybill generation dialog condition

* fix: excluding unidentified accounts from gstr-1

* fix: optimize reposting of sle and gle (#24694)

* fix: optimize update_gl_entries_after method

* fix: Optimized reposting patch

* fix: accounting dimensions

* added reload_doc in patch

* Update item_reposting_for_incorrect_sl_and_gl.py

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>

* fix: Replaced spaces with tabs

* fix: merge conflict

* fix: test cases

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
Co-authored-by: pateljannat <pateljannat2308@gmail.com>
Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
This commit is contained in:
Nabin Hait 2021-02-23 16:38:52 +05:30 committed by GitHub
parent 3248da9089
commit d46b23699c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 257 additions and 155 deletions

View File

@ -33,11 +33,11 @@ class AccountingDimension(Document):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self)
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long')
def on_trash(self):
if frappe.flags.in_test:
delete_accounting_dimension(doc=self)
delete_accounting_dimension(doc=self, queue='long')
else:
frappe.enqueue(delete_accounting_dimension, doc=self)
@ -48,6 +48,9 @@ class AccountingDimension(Document):
if not self.fieldname:
self.fieldname = scrub(self.label)
def on_update(self):
frappe.flags.accounting_dimensions = None
def make_dimension_in_accounting_doctypes(doc):
doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions())
@ -165,9 +168,9 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype)
def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
@ -176,12 +179,14 @@ def get_doctypes_with_dimensions():
return doclist
def get_accounting_dimensions(as_list=True):
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"])
if frappe.flags.accounting_dimensions is None:
frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"])
if as_list:
return [d.fieldname for d in accounting_dimensions]
return [d.fieldname for d in frappe.flags.accounting_dimensions]
else:
return accounting_dimensions
return frappe.flags.accounting_dimensions
def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs

View File

@ -27,30 +27,30 @@ class GLEntry(Document):
def validate(self):
self.flags.ignore_submit_comment = True
self.check_mandatory()
self.validate_and_set_fiscal_year()
self.pl_must_have_cost_center()
self.validate_cost_center()
if not self.flags.from_repost:
self.check_mandatory()
self.validate_cost_center()
self.check_pl_account()
self.validate_party()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost:
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost:
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj)
# Update outstanding amt on against voucher
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
# Update outstanding amt on against voucher
if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees']
and self.against_voucher and self.flags.update_outstanding == 'Yes'):
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
def check_mandatory(self):
mandatory = ['account','voucher_type','voucher_no','company']
@ -58,7 +58,7 @@ class GLEntry(Document):
if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
account_type = frappe.db.get_value("Account", self.account, "account_type")
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if not (self.party_type and self.party):
if account_type == "Receivable":
frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
@ -73,7 +73,7 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account))
def pl_must_have_cost_center(self):
if frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss":
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
.format(self.voucher_type, self.voucher_no, self.account))
@ -140,25 +140,16 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account, self.company))
def validate_cost_center(self):
if not hasattr(self, "cost_center_company"):
self.cost_center_company = {}
if not self.cost_center: return
def _get_cost_center_company():
if not self.cost_center_company.get(self.cost_center):
self.cost_center_company[self.cost_center] = frappe.db.get_value(
"Cost Center", self.cost_center, "company")
is_group, company = frappe.get_cached_value('Cost Center',
self.cost_center, ['is_group', 'company'])
return self.cost_center_company[self.cost_center]
def _check_is_group():
return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group'))
if self.cost_center and _get_cost_center_company() != self.company:
if company != self.company:
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
and self.cost_center and _check_is_group():
if (self.voucher_type != 'Period Closing Voucher' and is_group):
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
@ -184,7 +175,6 @@ class GLEntry(Document):
if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
def validate_balance_type(account, adv_adj=False):
if not adv_adj and account:
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
@ -250,7 +240,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.db.get_value("Account", account, "freeze_account")
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == 'Yes' and not adv_adj:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,
'frozen_accounts_modifier')

View File

@ -2,6 +2,7 @@
// For license information, please see license.txt
{% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts");
erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
setup(doc) {
@ -9,6 +10,10 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
this._super(doc);
},
company: function() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
},
onload(doc) {
this._super();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
@ -16,6 +21,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
this.frm.script_manager.trigger("is_pos");
this.frm.refresh_fields();
}
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
},
refresh(doc) {

View File

@ -93,7 +93,7 @@ class POSInvoice(SalesInvoice):
mode_of_payment=pay.mode_of_payment, status="Paid"),
fieldname="grand_total")
if pay.amount != paid_amt:
if paid_amt and pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
def validate_stock_availablility(self):
@ -311,7 +311,9 @@ class POSInvoice(SalesInvoice):
self.set(fieldname, profile.get(fieldname))
if self.customer:
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
customer_price_list, customer_group, customer_currency = frappe.db.get_value(
"Customer", self.customer, ['default_price_list', 'customer_group', 'default_currency']
)
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
if customer_currency != profile.get('currency'):
@ -322,6 +324,8 @@ class POSInvoice(SalesInvoice):
if selling_price_list:
self.set('selling_price_list', selling_price_list)
if customer_currency != profile.get('currency'):
self.set('currency', customer_currency)
# set pos values in items
for item in self.get("items"):

View File

@ -120,6 +120,7 @@ class POSInvoiceMergeLog(Document):
i.qty = i.qty + item.qty
if not found:
item.rate = item.net_rate
item.price_list_rate = 0
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
items.append(si_item)
@ -157,6 +158,8 @@ class POSInvoiceMergeLog(Document):
invoice.set('taxes', taxes)
invoice.additional_discount_percentage = 0
invoice.discount_amount = 0.0
invoice.taxes_and_charges = None
invoice.ignore_pricing_rule = 1
return invoice

View File

@ -12,8 +12,6 @@
"company",
"country",
"column_break_9",
"update_stock",
"ignore_pricing_rule",
"warehouse",
"campaign",
"company_address",
@ -25,8 +23,14 @@
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
"item_groups",
"column_break_16",
"update_stock",
"ignore_pricing_rule",
"allow_rate_change",
"allow_discount_change",
"section_break_23",
"item_groups",
"column_break_25",
"customer_groups",
"section_break_16",
"print_format",
@ -309,6 +313,7 @@
"default": "1",
"fieldname": "update_stock",
"fieldtype": "Check",
"hidden": 1,
"label": "Update Stock",
"read_only": 1
},
@ -329,13 +334,34 @@
"fieldname": "auto_add_item_to_cart",
"fieldtype": "Check",
"label": "Automatically Add Filtered Item To Cart"
},
{
"default": "0",
"fieldname": "allow_rate_change",
"fieldtype": "Check",
"label": "Allow User to Edit Rate"
},
{
"default": "0",
"fieldname": "allow_discount_change",
"fieldtype": "Check",
"label": "Allow User to Edit Discount"
},
{
"collapsible": 1,
"fieldname": "section_break_23",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_25",
"fieldtype": "Column Break"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-12-20 13:59:28.877572",
"modified": "2021-01-06 14:42:41.713864",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@ -456,7 +456,9 @@ class SalesInvoice(SellingController):
if not for_validate and not self.customer:
self.customer = pos.customer
self.ignore_pricing_rule = pos.ignore_pricing_rule
if not for_validate:
self.ignore_pricing_rule = pos.ignore_pricing_rule
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')

View File

@ -44,9 +44,9 @@ def validate_accounting_period(gl_map):
frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
def process_gl_map(gl_map, merge_entries=True):
def process_gl_map(gl_map, merge_entries=True, precision=None):
if merge_entries:
gl_map = merge_similar_entries(gl_map)
gl_map = merge_similar_entries(gl_map, precision)
for entry in gl_map:
# toggle debit, credit if negative entry
if flt(entry.debit) < 0:
@ -69,7 +69,7 @@ def process_gl_map(gl_map, merge_entries=True):
return gl_map
def merge_similar_entries(gl_map):
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
accounting_dimensions = get_accounting_dimensions()
for entry in gl_map:
@ -88,7 +88,9 @@ def merge_similar_entries(gl_map):
company = gl_map[0].company if gl_map else erpnext.get_default_company()
company_currency = erpnext.get_company_currency(company)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
if not precision:
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
# filter zero debit and credit entries
merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
@ -132,8 +134,8 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle.update(args)
gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
gle.flags.adv_adj = adv_adj
gle.flags.update_outstanding = update_outstanding or 'Yes'
gle.submit()
if not from_repost:

View File

@ -902,10 +902,9 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa
warehouse_account = get_warehouse_account_map(company)
gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
for voucher_type, voucher_no in stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):

View File

@ -302,6 +302,7 @@ class AccountsController(TransactionBase):
args["doctype"] = self.doctype
args["name"] = self.name
args["child_docname"] = item.name
args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0
if not args.get("transaction_date"):
args["transaction_date"] = args.get("posting_date")

View File

@ -473,13 +473,19 @@ class SellingController(StockController):
non_stock_items = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
duplicate_items_msg += "<br><br>"
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
get_link_to_form("Selling Settings", "Selling Settings")
)
if stock_items in check_list:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
frappe.throw(duplicate_items_msg)
else:
check_list.append(stock_items)
else:
if non_stock_items in chk_dupl_itm:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
frappe.throw(duplicate_items_msg)
else:
chk_dupl_itm.append(non_stock_items)

View File

@ -74,7 +74,7 @@ class StockController(AccountsController):
gl_list = []
warehouse_with_no_account = []
precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
precision = self.get_debit_field_precision()
for item_row in voucher_details:
sle_list = sle_map.get(item_row.name)
@ -131,7 +131,13 @@ class StockController(AccountsController):
if frappe.db.get_value("Warehouse", wh, "company"):
frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
return process_gl_map(gl_list)
return process_gl_map(gl_list, precision=precision)
def get_debit_field_precision(self):
if not frappe.flags.debit_field_precision:
frappe.flags.debit_field_precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
return frappe.flags.debit_field_precision
def update_stock_ledger_entries(self, sle):
sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
@ -244,7 +250,7 @@ class StockController(AccountsController):
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
else:
is_expense_account = frappe.db.get_value("Account",
is_expense_account = frappe.get_cached_value("Account",
item.get("expense_account"), "report_type")=="Profit and Loss"
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account:
frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account")
@ -492,7 +498,7 @@ class StockController(AccountsController):
elif not is_reposting_pending():
check_if_stock_and_account_balance_synced(self.posting_date,
self.company, self.doctype, self.name)
def is_reposting_pending():
return frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})

View File

@ -107,7 +107,7 @@ class calculate_taxes_and_totals(object):
elif item.discount_amount and item.pricing_rules:
item.rate = item.price_list_rate - item.discount_amount
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']:
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']:
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0:
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))

View File

@ -50,6 +50,7 @@ class TestMpesaSettings(unittest.TestCase):
create_mpesa_settings(payment_gateway_name="Payment")
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
frappe.db.set_value("Account", mpesa_account, "account_currency", "KES")
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES")
pos_invoice = create_pos_invoice(do_not_submit=1)
pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 500})
@ -195,6 +196,8 @@ class TestMpesaSettings(unittest.TestCase):
pr.delete()
pos_invoice.delete()
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
def create_mpesa_settings(payment_gateway_name="Express"):
if frappe.db.exists("Mpesa Settings", payment_gateway_name):
return frappe.get_doc("Mpesa Settings", payment_gateway_name)

View File

@ -21,8 +21,7 @@ class TestEmployee(unittest.TestCase):
from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders
employees_born_today = get_employees_who_are_born_today()
self.assertTrue(employees_born_today.get("_Test Company"))
self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()])
frappe.db.sql("delete from `tabEmail Queue`")

View File

@ -677,7 +677,7 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.rename_pos_closing_doctype
erpnext.patches.v13_0.replace_pos_payment_mode_table
erpnext.patches.v13_0.replace_pos_payment_mode_table #2020-12-29
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
execute:frappe.reload_doc("HR", "doctype", "Employee Advance")
@ -752,3 +752,4 @@ erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v12_0.add_state_code_for_ladakh
erpnext.patches.v13_0.update_vehicle_no_reqd_condition

View File

@ -4,11 +4,26 @@ from erpnext.stock.stock_ledger import update_entries_after
from erpnext.accounts.utils import update_gl_entries_after
def execute():
data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time
from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0
order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1)
frappe.reload_doc('stock', 'doctype', 'repost_item_valuation')
for index, d in enumerate(data):
reposting_project_deployed_on = frappe.db.get_value("DocType", "Repost Item Valuation", "creation")
data = frappe.db.sql('''
SELECT
name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time
FROM
`tabStock Ledger Entry`
WHERE
creation > %s
and is_cancelled = 0
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
''', reposting_project_deployed_on, as_dict=1)
frappe.db.auto_commit_on_many_writes = 1
print("Reposting Stock Ledger Entries...")
total_sle = len(data)
i = 0
for d in data:
update_entries_after({
"item_code": d.item_code,
"warehouse": d.warehouse,
@ -19,9 +34,13 @@ def execute():
"sle_id": d.name
}, allow_negative_stock=True)
frappe.db.auto_commit_on_many_writes = 1
i += 1
if i%100 == 0:
print(i, "/", total_sle)
print("Reposting General Ledger Entries...")
for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
update_gl_entries_after('2020-12-25', '01:58:55', company=row.name)
frappe.db.auto_commit_on_many_writes = 0
frappe.db.auto_commit_on_many_writes = 0

View File

@ -6,12 +6,10 @@ from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "POS Payment Method")
frappe.reload_doc("accounts", "doctype", "pos_payment_method")
pos_profiles = frappe.get_all("POS Profile")
for pos_profile in pos_profiles:
if not pos_profile.get("payments"): return
payments = frappe.db.sql("""
select idx, parentfield, parenttype, parent, mode_of_payment, `default` from `tabSales Invoice Payment` where parent=%s
""", pos_profile.name, as_dict=1)

View File

@ -0,0 +1,9 @@
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
if frappe.db.exists('Custom Field', { 'fieldname': 'vehicle_no' }):
frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', '')

View File

@ -41,40 +41,6 @@ class TestPayrollEntry(unittest.TestCase):
make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account=company_doc.default_payroll_payable_account,
currency=company_doc.default_currency)
def test_multi_currency_payroll_entry(self): # pylint: disable=no-self-use
company = erpnext.get_default_company()
employee = make_employee("test_muti_currency_employee@payroll.com", company=company)
for data in frappe.get_all('Salary Component', fields = ["name"]):
if not frappe.db.get_value('Salary Component Account',
{'parent': data.name, 'company': company}, 'name'):
get_salary_component_account(data.name)
company_doc = frappe.get_doc('Company', company)
salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
create_salary_structure_assignment(employee, salary_structure.name, company=company)
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
dates = get_start_end_dates('Monthly', nowdate())
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
payroll_entry.make_payment_entry()
salary_slip.load_from_db()
payroll_je = salary_slip.journal_entry
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
payment_entry = frappe.db.sql('''
Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
Where je.name = jea.parent
And jea.reference_name = %s
''', (payroll_entry.name), as_dict=1)
self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit)
self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit)
def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
for data in frappe.get_all('Salary Component', fields = ["name"]):

View File

@ -188,7 +188,6 @@ const get_ewaybill_fields = (frm) => {
'fieldname': 'vehicle_no',
'label': 'Vehicle No',
'fieldtype': 'Data',
'depends_on': 'eval:(doc.mode_of_transport === "Road")',
'default': frm.doc.vehicle_no
},
{

View File

@ -160,7 +160,7 @@ def get_item_list(invoice):
item.update(d.as_dict())
item.sr_no = d.idx
item.description = d.item_name.replace('"', '\\"')
item.description = json.dumps(d.item_name)[1:-1]
item.qty = abs(item.qty)
item.discount_amount = 0

View File

@ -236,6 +236,7 @@ class Gstr1Report(object):
self.cgst_sgst_invoices = []
unidentified_gst_accounts = []
unidentified_gst_accounts_invoice = []
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
if account in self.gst_accounts.cess_account:
self.invoice_cess.setdefault(parent, tax_amount)
@ -251,6 +252,7 @@ class Gstr1Report(object):
if not (cgst_or_sgst or account in self.gst_accounts.igst_account):
if "gst" in account.lower() and account not in unidentified_gst_accounts:
unidentified_gst_accounts.append(account)
unidentified_gst_accounts_invoice.append(parent)
continue
for item_code, tax_amounts in item_wise_tax_detail.items():
@ -273,7 +275,7 @@ class Gstr1Report(object):
# Build itemised tax for export invoices where tax table is blank
for invoice, items in iteritems(self.invoice_items):
if invoice not in self.items_based_on_tax_rate \
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())

View File

@ -110,9 +110,11 @@ def make_custom_fields():
'Purchase Order': purchase_invoice_fields + invoice_fields,
'Purchase Receipt': purchase_invoice_fields + invoice_fields,
'Sales Invoice': sales_invoice_fields + invoice_fields,
'POS Invoice': sales_invoice_fields + invoice_fields,
'Sales Order': sales_invoice_fields + invoice_fields,
'Delivery Note': sales_invoice_fields + invoice_fields,
'Sales Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
'POS Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields,
'Delivery Note Item': invoice_item_fields,

View File

@ -69,6 +69,10 @@ erpnext.PointOfSale.Controller = class {
dialog.fields_dict.balance_details.grid.refresh();
});
}
const pos_profile_query = {
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
filters: { company: frappe.defaults.get_default('company') }
}
const dialog = new frappe.ui.Dialog({
title: __('Create POS Opening Entry'),
static: true,
@ -80,6 +84,7 @@ erpnext.PointOfSale.Controller = class {
{
fieldtype: 'Link', label: __('POS Profile'),
options: 'POS Profile', fieldname: 'pos_profile', reqd: 1,
get_query: () => pos_profile_query,
onchange: () => fetch_pos_payment_methods()
},
{
@ -124,9 +129,8 @@ erpnext.PointOfSale.Controller = class {
});
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
Object.assign(this.settings, profile);
this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
this.settings.hide_images = profile.hide_images;
this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart;
this.make_app();
});
}
@ -255,11 +259,9 @@ erpnext.PointOfSale.Controller = class {
get_frm: () => this.frm,
cart_item_clicked: (item_code, batch_no, uom) => {
const item_row = this.frm.doc.items.find(
i => i.item_code === item_code
&& i.uom === uom
&& (!batch_no || (batch_no && i.batch_no === batch_no))
);
const search_field = batch_no ? 'batch_no' : 'item_code';
const search_value = batch_no || item_code;
const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom);
this.item_details.toggle_item_details_section(item_row);
},
@ -281,6 +283,7 @@ erpnext.PointOfSale.Controller = class {
init_item_details() {
this.item_details = new erpnext.PointOfSale.ItemDetails({
wrapper: this.$components_wrapper,
settings: this.settings,
events: {
get_frm: () => this.frm,
@ -415,6 +418,11 @@ erpnext.PointOfSale.Controller = class {
() => this.item_selector.toggle_component(true)
]);
},
delete_order: (name) => {
frappe.model.delete_doc(this.frm.doc.doctype, name, () => {
this.recent_order_list.refresh_list();
});
},
new_order: () => {
frappe.run_serially([
() => frappe.dom.freeze(),
@ -696,14 +704,14 @@ erpnext.PointOfSale.Controller = class {
frappe.dom.freeze();
const { doctype, name, current_item } = this.item_details;
frappe.model.set_value(doctype, name, 'qty', 0);
this.frm.script_manager.trigger('qty', doctype, name).then(() => {
frappe.model.clear_doc(doctype, name);
this.update_cart_html(current_item, true);
this.item_details.toggle_item_details_section(undefined);
frappe.dom.unfreeze();
})
frappe.model.set_value(doctype, name, 'qty', 0)
.then(() => {
frappe.model.clear_doc(doctype, name);
this.update_cart_html(current_item, true);
this.item_details.toggle_item_details_section(undefined);
frappe.dom.unfreeze();
})
.catch(e => console.log(e));
}
}

View File

@ -5,6 +5,8 @@ erpnext.PointOfSale.ItemCart = class {
this.customer_info = undefined;
this.hide_images = settings.hide_images;
this.allowed_customer_groups = settings.customer_groups;
this.allow_rate_change = settings.allow_rate_change;
this.allow_discount_change = settings.allow_discount_change;
this.init_component();
}
@ -201,7 +203,7 @@ erpnext.PointOfSale.ItemCart = class {
me.events.checkout();
me.toggle_checkout_btn(false);
me.$add_discount_elem.removeClass("d-none");
me.allow_discount_change && me.$add_discount_elem.removeClass("d-none");
});
this.$totals_section.on('click', '.edit-cart-btn', () => {
@ -479,8 +481,8 @@ erpnext.PointOfSale.ItemCart = class {
update_totals_section(frm) {
if (!frm) frm = this.events.get_frm();
this.render_net_total(frm.doc.base_net_total);
this.render_grand_total(frm.doc.base_grand_total);
this.render_net_total(frm.doc.net_total);
this.render_grand_total(frm.doc.grand_total);
const taxes = frm.doc.taxes.map(t => {
return {
@ -549,7 +551,7 @@ erpnext.PointOfSale.ItemCart = class {
get_cart_item({ item_code, batch_no, uom }) {
const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
const uom_attr = `[data-uom=${escape(uom)}]`;
const uom_attr = `[data-uom="${escape(uom)}"]`;
const item_selector = batch_no ?
`.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
@ -671,7 +673,7 @@ erpnext.PointOfSale.ItemCart = class {
update_selector_value_in_cart_item(selector, value, item) {
const $item_to_update = this.get_cart_item(item);
$item_to_update.attr(`data-${selector}`, value);
$item_to_update.attr(`data-${selector}`, escape(value));
}
toggle_checkout_btn(show_checkout) {
@ -706,14 +708,26 @@ erpnext.PointOfSale.ItemCart = class {
on_numpad_event($btn) {
const current_action = $btn.attr('data-button-value');
const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action);
this.highlight_numpad_btn($btn, current_action);
const action_is_allowed = action_is_field_edit ? (
(current_action == 'rate' && this.allow_rate_change) ||
(current_action == 'discount_percentage' && this.allow_discount_change) ||
(current_action == 'qty')) : true;
const action_is_pressed_twice = this.prev_action === current_action;
const first_click_event = !this.prev_action;
const field_to_edit_changed = this.prev_action && this.prev_action != current_action;
if (action_is_field_edit) {
if (!action_is_allowed) {
const label = current_action == 'rate' ? 'Rate'.bold() : 'Discount'.bold();
const message = __('Editing {0} is not allowed as per POS Profile settings', [label]);
frappe.show_alert({
indicator: 'red',
message: message
});
frappe.utils.play_sound("error");
return;
}
if (first_click_event || field_to_edit_changed) {
this.prev_action = current_action;
@ -757,6 +771,7 @@ erpnext.PointOfSale.ItemCart = class {
this.numpad_value = current_action;
}
this.highlight_numpad_btn($btn, current_action);
this.events.numpad_event(this.numpad_value, this.prev_action);
}

View File

@ -1,7 +1,9 @@
erpnext.PointOfSale.ItemDetails = class {
constructor({ wrapper, events }) {
constructor({ wrapper, events, settings }) {
this.wrapper = wrapper;
this.events = events;
this.allow_rate_change = settings.allow_rate_change;
this.allow_discount_change = settings.allow_discount_change;
this.current_item = {};
this.init_component();
@ -207,17 +209,27 @@ erpnext.PointOfSale.ItemDetails = class {
bind_custom_control_change_event() {
const me = this;
if (this.rate_control) {
this.rate_control.df.onchange = function() {
if (this.value || flt(this.value) === 0) {
me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
const item_row = frappe.get_doc(me.doctype, me.name);
const doc = me.events.get_frm().doc;
me.$item_price.html(format_currency(item_row.rate, doc.currency));
me.render_discount_dom(item_row);
});
}
if (this.allow_rate_change) {
this.rate_control.df.onchange = function() {
if (this.value || flt(this.value) === 0) {
me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
const item_row = frappe.get_doc(me.doctype, me.name);
const doc = me.events.get_frm().doc;
me.$item_price.html(format_currency(item_row.rate, doc.currency));
me.render_discount_dom(item_row);
});
}
};
} else {
this.rate_control.df.read_only = 1;
}
this.rate_control.refresh();
}
if (this.discount_percentage_control && !this.allow_discount_change) {
this.discount_percentage_control.df.read_only = 1;
this.discount_percentage_control.refresh();
}
if (this.warehouse_control) {
@ -294,8 +306,16 @@ erpnext.PointOfSale.ItemDetails = class {
}
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
const { item_code, batch_no, uom } = this.current_item;
const item_code_is_same = item_code === item_row.item_code;
const batch_is_same = batch_no == item_row.batch_no;
const uom_is_same = uom === item_row.uom;
// check if current_item is same as item_row
const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false;
const field_control = me[`${fieldname}_control`];
if (field_control) {
if (item_is_same && field_control && field_control.get_value() !== value) {
field_control.set_value(value);
cur_pos.update_cart_html(item_row);
}

View File

@ -265,6 +265,14 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.$summary_wrapper.addClass('d-none');
});
this.$summary_container.on('click', '.delete-btn', () => {
this.events.delete_order(this.doc.name);
this.show_summary_placeholder();
// this.toggle_component(false);
// this.$component.find('.no-summary-placeholder').removeClass('d-none');
// this.$summary_wrapper.addClass('d-none');
});
this.$summary_container.on('click', '.new-btn', () => {
this.events.new_order();
this.toggle_component(false);
@ -401,7 +409,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }];
return [
{ condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] },
{ condition: this.doc.docstatus === 0, visible_btns: ['Edit Order', 'Delete Order'] },
{ condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']},
{ condition: this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt']}
];

View File

@ -672,13 +672,14 @@ class Item(WebsiteGenerator):
if not records: return
document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations")
msg = _("The items {0} and {1} are present in the following {2} : <br>"
.format(frappe.bold(old_name), frappe.bold(new_name), document))
msg = _("The items {0} and {1} are present in the following {2} : ").format(
frappe.bold(old_name), frappe.bold(new_name), document)
msg += '<br>'
msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
.format(frappe.bold(old_name)))
msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format(
frappe.bold(old_name))
frappe.throw(_(msg), title=_("Merge not allowed"))
@ -971,7 +972,7 @@ class Item(WebsiteGenerator):
frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field))))
def check_if_linked_document_exists(self, field):
linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "Purchase Receipt Item",
linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item",
"Purchase Invoice Item", "Stock Entry Detail", "Stock Reconciliation Item"]
# For "Is Stock Item", following doctypes is important

View File

@ -19,7 +19,7 @@ from erpnext.stock.doctype.item_manufacturer.item_manufacturer import get_item_m
from six import string_types, iteritems
sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']
sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'POS Invoice']
purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
@frappe.whitelist()

View File

@ -58,8 +58,9 @@ def validate_cancellation(args):
if repost_entry.status == 'In Progress':
frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet."))
if repost_entry.status == 'Queued':
frappe.delete_doc("Repost Item Valuation", repost_entry.name)
doc = frappe.get_doc("Repost Item Valuation", repost_entry.name)
doc.cancel()
doc.delete()
def set_as_cancel(voucher_type, voucher_no):
frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1,
@ -133,7 +134,7 @@ class update_entries_after(object):
self.item_code = args.get("item_code")
if self.args.sle_id:
self.args['name'] = self.args.sle_id
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
self.get_precision()
self.valuation_method = get_valuation_method(self.item_code)
@ -201,7 +202,7 @@ class update_entries_after(object):
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
order by timestamp(posting_date, posting_time) desc, creation desc
limit 1""", args, as_dict=1)
return sle[0] if sle else frappe._dict()
@ -224,7 +225,7 @@ class update_entries_after(object):
if sle.dependant_sle_voucher_detail_no:
entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle)
self.update_bin()
if self.exceptions:
@ -439,7 +440,7 @@ class update_entries_after(object):
# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"):
doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no)
doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
doc.update_valuation_rate(reset_outgoing_rate=False)
for d in (doc.items + doc.supplied_items):
d.db_update()