Merge branch 'develop' into bg_query

This commit is contained in:
Deepesh Garg 2022-11-13 19:47:43 +05:30 committed by GitHub
commit a2260a3dc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 679 additions and 246 deletions

View File

@ -59,7 +59,7 @@ jobs:
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: '3.10' python-version: '3.11'
- name: Check for valid Python & Merge Conflicts - name: Check for valid Python & Merge Conflicts
run: | run: |

View File

@ -52,7 +52,7 @@ def validate_company(company):
if parent_company and (not allow_account_creation_against_child_company): if parent_company and (not allow_account_creation_against_child_company):
msg = _("{} is a child company.").format(frappe.bold(company)) + " " msg = _("{} is a child company.").format(frappe.bold(company)) + " "
msg += _("Please import accounts against parent company or enable {} in company master.").format( msg += _("Please import accounts against parent company or enable {} in company master.").format(
frappe.bold("Allow Account Creation Against Child Company") frappe.bold(_("Allow Account Creation Against Child Company"))
) )
frappe.throw(msg, title=_("Wrong Company")) frappe.throw(msg, title=_("Wrong Company"))

View File

@ -23,6 +23,7 @@
"fetch_customers", "fetch_customers",
"column_break_6", "column_break_6",
"primary_mandatory", "primary_mandatory",
"show_net_values_in_party_account",
"column_break_17", "column_break_17",
"customers", "customers",
"preferences", "preferences",
@ -291,10 +292,16 @@
"fieldname": "include_break", "fieldname": "include_break",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Page Break After Each SoA" "label": "Page Break After Each SoA"
},
{
"default": "0",
"fieldname": "show_net_values_in_party_account",
"fieldtype": "Check",
"label": "Show Net Values in Party Account"
} }
], ],
"links": [], "links": [],
"modified": "2022-10-17 17:47:08.662475", "modified": "2022-11-10 17:44:17.165991",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Process Statement Of Accounts", "name": "Process Statement Of Accounts",

View File

@ -95,6 +95,7 @@ def get_report_pdf(doc, consolidated=True):
"show_opening_entries": 0, "show_opening_entries": 0,
"include_default_book_entries": 0, "include_default_book_entries": 0,
"tax_id": tax_id if tax_id else None, "tax_id": tax_id if tax_id else None,
"show_net_values_in_party_account": doc.show_net_values_in_party_account,
} }
) )
col, res = get_soa(filters) col, res = get_soa(filters)

View File

@ -1410,7 +1410,7 @@ class PurchaseInvoice(BuyingController):
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
self.update_project() self.update_project()
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ( self.ignore_linked_doctypes = (
@ -1463,6 +1463,7 @@ class PurchaseInvoice(BuyingController):
def update_billing_status_in_pr(self, update_modified=True): def update_billing_status_in_pr(self, update_modified=True):
updated_pr = [] updated_pr = []
po_details = []
for d in self.get("items"): for d in self.get("items"):
if d.pr_detail: if d.pr_detail:
billed_amt = frappe.db.sql( billed_amt = frappe.db.sql(
@ -1480,7 +1481,10 @@ class PurchaseInvoice(BuyingController):
) )
updated_pr.append(d.purchase_receipt) updated_pr.append(d.purchase_receipt)
elif d.po_detail: elif d.po_detail:
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified) po_details.append(d.po_detail)
if po_details:
updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
for pr in set(updated_pr): for pr in set(updated_pr):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage

View File

@ -373,7 +373,7 @@ class SalesInvoice(SellingController):
if self.update_stock == 1: if self.update_stock == 1:
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
self.db_set("repost_required", 0) self.db_set("repost_required", 0)
if ( if (
@ -2399,7 +2399,7 @@ def get_loyalty_programs(customer):
lp_details = get_loyalty_programs(customer) lp_details = get_loyalty_programs(customer)
if len(lp_details) == 1: if len(lp_details) == 1:
frappe.db.set(customer, "loyalty_program", lp_details[0]) customer.db_set("loyalty_program", lp_details[0])
return lp_details return lp_details
else: else:
return lp_details return lp_details

View File

@ -3,7 +3,8 @@
import frappe import frappe
from frappe import _, scrub from frappe import _, qb, scrub
from frappe.query_builder import Order
from frappe.utils import cint, flt, formatdate from frappe.utils import cint, flt, formatdate
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
@ -398,6 +399,7 @@ class GrossProfitGenerator(object):
self.average_buying_rate = {} self.average_buying_rate = {}
self.filters = frappe._dict(filters) self.filters = frappe._dict(filters)
self.load_invoice_items() self.load_invoice_items()
self.get_delivery_notes()
if filters.group_by == "Invoice": if filters.group_by == "Invoice":
self.group_items_by_invoice() self.group_items_by_invoice()
@ -591,6 +593,21 @@ class GrossProfitGenerator(object):
return flt(buying_amount, self.currency_precision) return flt(buying_amount, self.currency_precision)
def calculate_buying_amount_from_sle(self, row, my_sle, parenttype, parent, item_row, item_code):
for i, sle in enumerate(my_sle):
# find the stock valution rate from stock ledger entry
if (
sle.voucher_type == parenttype
and parent == sle.voucher_no
and sle.voucher_detail_no == item_row
):
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
if previous_stock_value:
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
def get_buying_amount(self, row, item_code): def get_buying_amount(self, row, item_code):
# IMP NOTE # IMP NOTE
# stock_ledger_entries should already be filtered by item_code and warehouse and # stock_ledger_entries should already be filtered by item_code and warehouse and
@ -607,19 +624,22 @@ class GrossProfitGenerator(object):
if row.dn_detail: if row.dn_detail:
parenttype, parent = "Delivery Note", row.delivery_note parenttype, parent = "Delivery Note", row.delivery_note
for i, sle in enumerate(my_sle): return self.calculate_buying_amount_from_sle(
# find the stock valution rate from stock ledger entry row, my_sle, parenttype, parent, row.item_row, item_code
if ( )
sle.voucher_type == parenttype elif self.delivery_notes.get((row.parent, row.item_code), None):
and parent == sle.voucher_no # check if Invoice has delivery notes
and sle.voucher_detail_no == row.item_row dn = self.delivery_notes.get((row.parent, row.item_code))
): parenttype, parent, item_row, warehouse = (
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0 "Delivery Note",
dn["delivery_note"],
if previous_stock_value: dn["item_row"],
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) dn["warehouse"],
else: )
return flt(row.qty) * self.get_average_buying_rate(row, item_code) my_sle = self.sle.get((item_code, warehouse))
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
else: else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code) return flt(row.qty) * self.get_average_buying_rate(row, item_code)
@ -753,6 +773,29 @@ class GrossProfitGenerator(object):
as_dict=1, as_dict=1,
) )
def get_delivery_notes(self):
self.delivery_notes = frappe._dict({})
if self.si_list:
invoices = [x.parent for x in self.si_list]
dni = qb.DocType("Delivery Note Item")
delivery_notes = (
qb.from_(dni)
.select(
dni.against_sales_invoice.as_("sales_invoice"),
dni.item_code,
dni.warehouse,
dni.parent.as_("delivery_note"),
dni.name.as_("item_row"),
)
.where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices)))
.groupby(dni.against_sales_invoice, dni.item_code)
.orderby(dni.creation, order=Order.desc)
.run(as_dict=True)
)
for entry in delivery_notes:
self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry
def group_items_by_invoice(self): def group_items_by_invoice(self):
""" """
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children. Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.

View File

@ -0,0 +1,209 @@
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, nowdate
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.gross_profit.gross_profit import execute
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestGrossProfit(FrappeTestCase):
def setUp(self):
self.create_company()
self.create_item()
self.create_customer()
self.create_sales_invoice()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def create_company(self):
company_name = "_Test Gross Profit"
abbr = "_GP"
if frappe.db.exists("Company", company_name):
company = frappe.get_doc("Company", company_name)
else:
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": company_name,
"country": "India",
"default_currency": "INR",
"create_chart_of_accounts_based_on": "Standard Template",
"chart_of_accounts": "Standard",
}
)
company = company.save()
self.company = company.name
self.cost_center = company.cost_center
self.warehouse = "Stores - " + abbr
self.income_account = "Sales - " + abbr
self.expense_account = "Cost of Goods Sold - " + abbr
self.debit_to = "Debtors - " + abbr
self.creditors = "Creditors - " + abbr
def create_item(self):
item = create_item(
item_code="_Test GP Item", is_stock_item=1, company=self.company, warehouse=self.warehouse
)
self.item = item if isinstance(item, str) else item.item_code
def create_customer(self):
name = "_Test GP Customer"
if frappe.db.exists("Customer", name):
self.customer = name
else:
customer = frappe.new_doc("Customer")
customer.customer_name = name
customer.type = "Individual"
customer.save()
self.customer = customer.name
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
sinv = create_sales_invoice(
qty=qty,
rate=rate,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return sinv
def clear_old_entries(self):
doctype_list = [
"Sales Invoice",
"GL Entry",
"Payment Ledger Entry",
"Stock Entry",
"Stock Ledger Entry",
"Delivery Note",
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def test_invoice_without_only_delivery_note(self):
"""
Test buying amount for Invoice without `update_stock` flag set but has Delivery Note
"""
se = make_stock_entry(
company=self.company,
item_code=self.item,
target=self.warehouse,
qty=1,
basic_rate=100,
do_not_submit=True,
)
item = se.items[0]
se.append(
"items",
{
"item_code": item.item_code,
"s_warehouse": item.s_warehouse,
"t_warehouse": item.t_warehouse,
"qty": 1,
"basic_rate": 200,
"conversion_factor": item.conversion_factor or 1.0,
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"cost_center": item.cost_center,
"expense_account": item.expense_account,
},
)
se = se.save().submit()
sinv = create_sales_invoice(
qty=1,
rate=100,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
income_account=self.income_account,
expense_account=self.expense_account,
)
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
# Without Delivery Note, buying rate should be 150
expected_entry_without_dn = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 150.0,
"selling_amount": 100.0,
"buying_amount": 150.0,
"gross_profit": -50.0,
"gross_profit_%": -50.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry_without_dn, gp_entry[0])
# make delivery note
dn = make_delivery_note(sinv.name)
dn.items[0].qty = 1
dn = dn.save().submit()
columns, data = execute(filters=filters)
# Without Delivery Note, buying rate should be 100
expected_entry_with_dn = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 100.0,
"selling_amount": 100.0,
"buying_amount": 100.0,
"gross_profit": 0.0,
"gross_profit_%": 0.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry_with_dn, gp_entry[0])

View File

@ -361,7 +361,7 @@ class PurchaseOrder(BuyingController):
self.update_reserved_qty_for_subcontract() self.update_reserved_qty_for_subcontract()
self.check_on_hold_or_closed_status() self.check_on_hold_or_closed_status()
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
self.update_prevdoc_status() self.update_prevdoc_status()

View File

@ -31,7 +31,7 @@ class RequestforQuotation(BuyingController):
if self.docstatus < 1: if self.docstatus < 1:
# after amend and save, status still shows as cancelled, until submit # after amend and save, status still shows as cancelled, until submit
frappe.db.set(self, "status", "Draft") self.db_set("status", "Draft")
def validate_duplicate_supplier(self): def validate_duplicate_supplier(self):
supplier_list = [d.supplier for d in self.suppliers] supplier_list = [d.supplier for d in self.suppliers]
@ -73,14 +73,14 @@ class RequestforQuotation(BuyingController):
) )
def on_submit(self): def on_submit(self):
frappe.db.set(self, "status", "Submitted") self.db_set("status", "Submitted")
for supplier in self.suppliers: for supplier in self.suppliers:
supplier.email_sent = 0 supplier.email_sent = 0
supplier.quote_status = "Pending" supplier.quote_status = "Pending"
self.send_to_supplier() self.send_to_supplier()
def on_cancel(self): def on_cancel(self):
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
@frappe.whitelist() @frappe.whitelist()
def get_supplier_email_preview(self, supplier): def get_supplier_email_preview(self, supplier):

View File

@ -10,34 +10,37 @@
"document_type": "Setup", "document_type": "Setup",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"basic_info",
"naming_series", "naming_series",
"supplier_name", "supplier_name",
"country", "country",
"default_bank_account",
"tax_id",
"tax_category",
"tax_withholding_category",
"image",
"column_break0", "column_break0",
"supplier_group", "supplier_group",
"supplier_type", "supplier_type",
"allow_purchase_invoice_creation_without_purchase_order",
"allow_purchase_invoice_creation_without_purchase_receipt",
"is_internal_supplier",
"represents_company",
"disabled",
"is_transporter", "is_transporter",
"warn_rfqs", "image",
"warn_pos", "defaults_section",
"prevent_rfqs",
"prevent_pos",
"allowed_to_transact_section",
"companies",
"section_break_7",
"default_currency", "default_currency",
"default_bank_account",
"column_break_10", "column_break_10",
"default_price_list", "default_price_list",
"payment_terms",
"internal_supplier_section",
"is_internal_supplier",
"represents_company",
"column_break_16",
"companies",
"column_break2",
"supplier_details",
"column_break_30",
"website",
"language",
"dashboard_tab",
"tax_tab",
"tax_id",
"column_break_27",
"tax_category",
"tax_withholding_category",
"contact_and_address_tab",
"address_contacts", "address_contacts",
"address_html", "address_html",
"column_break1", "column_break1",
@ -49,30 +52,25 @@
"column_break_44", "column_break_44",
"supplier_primary_address", "supplier_primary_address",
"primary_address", "primary_address",
"default_payable_accounts", "accounting_tab",
"accounts", "accounts",
"section_credit_limit", "settings_tab",
"payment_terms", "allow_purchase_invoice_creation_without_purchase_order",
"cb_21", "allow_purchase_invoice_creation_without_purchase_receipt",
"column_break_54",
"is_frozen",
"disabled",
"warn_rfqs",
"warn_pos",
"prevent_rfqs",
"prevent_pos",
"block_supplier_section",
"on_hold", "on_hold",
"hold_type", "hold_type",
"release_date", "column_break_59",
"default_tax_withholding_config", "release_date"
"column_break2",
"website",
"supplier_details",
"column_break_30",
"language",
"is_frozen"
], ],
"fields": [ "fields": [
{
"fieldname": "basic_info",
"fieldtype": "Section Break",
"label": "Name and Type",
"oldfieldtype": "Section Break",
"options": "fa fa-user"
},
{ {
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
@ -192,6 +190,7 @@
"default": "0", "default": "0",
"fieldname": "warn_rfqs", "fieldname": "warn_rfqs",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1,
"label": "Warn RFQs", "label": "Warn RFQs",
"read_only": 1 "read_only": 1
}, },
@ -199,6 +198,7 @@
"default": "0", "default": "0",
"fieldname": "warn_pos", "fieldname": "warn_pos",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1,
"label": "Warn POs", "label": "Warn POs",
"read_only": 1 "read_only": 1
}, },
@ -206,6 +206,7 @@
"default": "0", "default": "0",
"fieldname": "prevent_rfqs", "fieldname": "prevent_rfqs",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1,
"label": "Prevent RFQs", "label": "Prevent RFQs",
"read_only": 1 "read_only": 1
}, },
@ -213,15 +214,10 @@
"default": "0", "default": "0",
"fieldname": "prevent_pos", "fieldname": "prevent_pos",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1,
"label": "Prevent POs", "label": "Prevent POs",
"read_only": 1 "read_only": 1
}, },
{
"depends_on": "represents_company",
"fieldname": "allowed_to_transact_section",
"fieldtype": "Section Break",
"label": "Allowed To Transact With"
},
{ {
"depends_on": "represents_company", "depends_on": "represents_company",
"fieldname": "companies", "fieldname": "companies",
@ -229,12 +225,6 @@
"label": "Allowed To Transact With", "label": "Allowed To Transact With",
"options": "Allowed To Transact With" "options": "Allowed To Transact With"
}, },
{
"collapsible": 1,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"label": "Currency and Price List"
},
{ {
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
@ -254,22 +244,12 @@
"label": "Price List", "label": "Price List",
"options": "Price List" "options": "Price List"
}, },
{
"collapsible": 1,
"fieldname": "section_credit_limit",
"fieldtype": "Section Break",
"label": "Payment Terms"
},
{ {
"fieldname": "payment_terms", "fieldname": "payment_terms",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Default Payment Terms Template", "label": "Default Payment Terms Template",
"options": "Payment Terms Template" "options": "Payment Terms Template"
}, },
{
"fieldname": "cb_21",
"fieldtype": "Column Break"
},
{ {
"default": "0", "default": "0",
"fieldname": "on_hold", "fieldname": "on_hold",
@ -315,13 +295,6 @@
"label": "Contact HTML", "label": "Contact HTML",
"read_only": 1 "read_only": 1
}, },
{
"collapsible": 1,
"collapsible_depends_on": "accounts",
"fieldname": "default_payable_accounts",
"fieldtype": "Section Break",
"label": "Default Payable Accounts"
},
{ {
"description": "Mention if non-standard payable account", "description": "Mention if non-standard payable account",
"fieldname": "accounts", "fieldname": "accounts",
@ -329,12 +302,6 @@
"label": "Accounts", "label": "Accounts",
"options": "Party Account" "options": "Party Account"
}, },
{
"collapsible": 1,
"fieldname": "default_tax_withholding_config",
"fieldtype": "Section Break",
"label": "Default Tax Withholding Config"
},
{ {
"collapsible": 1, "collapsible": 1,
"collapsible_depends_on": "supplier_details", "collapsible_depends_on": "supplier_details",
@ -383,7 +350,7 @@
{ {
"fieldname": "primary_address_and_contact_detail_section", "fieldname": "primary_address_and_contact_detail_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Primary Address and Contact Detail" "label": "Primary Address and Contact"
}, },
{ {
"description": "Reselect, if the chosen contact is edited after save", "description": "Reselect, if the chosen contact is edited after save",
@ -420,6 +387,64 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Supplier Primary Address", "label": "Supplier Primary Address",
"options": "Address" "options": "Address"
},
{
"fieldname": "dashboard_tab",
"fieldtype": "Tab Break",
"label": "Dashboard",
"show_dashboard": 1
},
{
"fieldname": "settings_tab",
"fieldtype": "Tab Break",
"label": "Settings"
},
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
},
{
"fieldname": "accounting_tab",
"fieldtype": "Tab Break",
"label": "Accounting"
},
{
"fieldname": "defaults_section",
"fieldtype": "Section Break",
"label": "Defaults"
},
{
"fieldname": "tax_tab",
"fieldtype": "Tab Break",
"label": "Tax"
},
{
"collapsible": 1,
"fieldname": "internal_supplier_section",
"fieldtype": "Section Break",
"label": "Internal Supplier"
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_27",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_54",
"fieldtype": "Column Break"
},
{
"fieldname": "block_supplier_section",
"fieldtype": "Section Break",
"label": "Block Supplier"
},
{
"fieldname": "column_break_59",
"fieldtype": "Column Break"
} }
], ],
"icon": "fa fa-user", "icon": "fa fa-user",
@ -432,7 +457,7 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2022-04-16 18:02:27.838623", "modified": "2022-11-09 18:02:59.075203",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier", "name": "Supplier",

View File

@ -145,7 +145,7 @@ class Supplier(TransactionBase):
def after_rename(self, olddn, newdn, merge=False): def after_rename(self, olddn, newdn, merge=False):
if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name": if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name":
frappe.db.set(self, "supplier_name", newdn) self.db_set("supplier_name", newdn)
@frappe.whitelist() @frappe.whitelist()

View File

@ -30,11 +30,11 @@ class SupplierQuotation(BuyingController):
self.validate_valid_till() self.validate_valid_till()
def on_submit(self): def on_submit(self):
frappe.db.set(self, "status", "Submitted") self.db_set("status", "Submitted")
self.update_rfq_supplier_status(1) self.update_rfq_supplier_status(1)
def on_cancel(self): def on_cancel(self):
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
self.update_rfq_supplier_status(0) self.update_rfq_supplier_status(0)
def on_trash(self): def on_trash(self):

View File

@ -60,7 +60,7 @@ class Opportunity(TransactionBase, CRMNote):
if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field): if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field):
try: try:
value = frappe.db.get_value(self.opportunity_from, self.party_name, field) value = frappe.db.get_value(self.opportunity_from, self.party_name, field)
frappe.db.set(self, field, value) self.db_set(field, value)
except Exception: except Exception:
continue continue

View File

@ -576,8 +576,8 @@ def regenerate_repayment_schedule(loan, cancel=0):
loan_doc = frappe.get_doc("Loan", loan) loan_doc = frappe.get_doc("Loan", loan)
next_accrual_date = None next_accrual_date = None
accrued_entries = 0 accrued_entries = 0
last_repayment_amount = 0 last_repayment_amount = None
last_balance_amount = 0 last_balance_amount = None
for term in reversed(loan_doc.get("repayment_schedule")): for term in reversed(loan_doc.get("repayment_schedule")):
if not term.is_accrued: if not term.is_accrued:
@ -585,9 +585,9 @@ def regenerate_repayment_schedule(loan, cancel=0):
loan_doc.remove(term) loan_doc.remove(term)
else: else:
accrued_entries += 1 accrued_entries += 1
if not last_repayment_amount: if last_repayment_amount is None:
last_repayment_amount = term.total_payment last_repayment_amount = term.total_payment
if not last_balance_amount: if last_balance_amount is None:
last_balance_amount = term.balance_loan_amount last_balance_amount = term.balance_loan_amount
loan_doc.save() loan_doc.save()

View File

@ -119,7 +119,7 @@ class MaintenanceSchedule(TransactionBase):
event.add_participant(self.doctype, self.name) event.add_participant(self.doctype, self.name)
event.insert(ignore_permissions=1) event.insert(ignore_permissions=1)
frappe.db.set(self, "status", "Submitted") self.db_set("status", "Submitted")
def create_schedule_list(self, start_date, end_date, no_of_visit, sales_person): def create_schedule_list(self, start_date, end_date, no_of_visit, sales_person):
schedule_list = [] schedule_list = []
@ -245,7 +245,7 @@ class MaintenanceSchedule(TransactionBase):
self.generate_schedule() self.generate_schedule()
def on_update(self): def on_update(self):
frappe.db.set(self, "status", "Draft") self.db_set("status", "Draft")
def update_amc_date(self, serial_nos, amc_expiry_date=None): def update_amc_date(self, serial_nos, amc_expiry_date=None):
for serial_no in serial_nos: for serial_no in serial_nos:
@ -344,7 +344,7 @@ class MaintenanceSchedule(TransactionBase):
if d.serial_no: if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no) serial_nos = get_valid_serial_nos(d.serial_no)
self.update_amc_date(serial_nos) self.update_amc_date(serial_nos)
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
delete_events(self.doctype, self.name) delete_events(self.doctype, self.name)
def on_trash(self): def on_trash(self):

View File

@ -125,12 +125,12 @@ class MaintenanceVisit(TransactionBase):
def on_submit(self): def on_submit(self):
self.update_customer_issue(1) self.update_customer_issue(1)
frappe.db.set(self, "status", "Submitted") self.db_set("status", "Submitted")
self.update_status_and_actual_date() self.update_status_and_actual_date()
def on_cancel(self): def on_cancel(self):
self.check_if_last_visit() self.check_if_last_visit()
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
self.update_status_and_actual_date(cancel=True) self.update_status_and_actual_date(cancel=True)
def on_update(self): def on_update(self):

View File

@ -206,8 +206,8 @@ class BOM(WebsiteGenerator):
self.manage_default_bom() self.manage_default_bom()
def on_cancel(self): def on_cancel(self):
frappe.db.set(self, "is_active", 0) self.db_set("is_active", 0)
frappe.db.set(self, "is_default", 0) self.db_set("is_default", 0)
# check if used in any other bom # check if used in any other bom
self.validate_bom_links() self.validate_bom_links()
@ -449,10 +449,10 @@ class BOM(WebsiteGenerator):
not frappe.db.exists(dict(doctype="BOM", docstatus=1, item=self.item, is_default=1)) not frappe.db.exists(dict(doctype="BOM", docstatus=1, item=self.item, is_default=1))
and self.is_active and self.is_active
): ):
frappe.db.set(self, "is_default", 1) self.db_set("is_default", 1)
frappe.db.set_value("Item", self.item, "default_bom", self.name) frappe.db.set_value("Item", self.item, "default_bom", self.name)
else: else:
frappe.db.set(self, "is_default", 0) self.db_set("is_default", 0)
item = frappe.get_doc("Item", self.item) item = frappe.get_doc("Item", self.item)
if item.default_bom == self.name: if item.default_bom == self.name:
frappe.db.set_value("Item", self.item, "default_bom", None) frappe.db.set_value("Item", self.item, "default_bom", None)

View File

@ -54,6 +54,9 @@ class JobCard(Document):
self.set_onload("job_card_excess_transfer", excess_transfer) self.set_onload("job_card_excess_transfer", excess_transfer)
self.set_onload("work_order_closed", self.is_work_order_closed()) self.set_onload("work_order_closed", self.is_work_order_closed())
def before_validate(self):
self.set_wip_warehouse()
def validate(self): def validate(self):
self.validate_time_logs() self.validate_time_logs()
self.set_status() self.set_status()
@ -639,6 +642,12 @@ class JobCard(Document):
if update_status: if update_status:
self.db_set("status", self.status) self.db_set("status", self.status)
def set_wip_warehouse(self):
if not self.wip_warehouse:
self.wip_warehouse = frappe.db.get_single_value(
"Manufacturing Settings", "default_wip_warehouse"
)
def validate_operation_id(self): def validate_operation_id(self):
if ( if (
self.get("operation_id") self.get("operation_id")

View File

@ -146,7 +146,7 @@ class WorkOrder(Document):
frappe.throw(_("Sales Order {0} is {1}").format(self.sales_order, status)) frappe.throw(_("Sales Order {0} is {1}").format(self.sales_order, status))
def set_default_warehouse(self): def set_default_warehouse(self):
if not self.wip_warehouse: if not self.wip_warehouse and not self.skip_transfer:
self.wip_warehouse = frappe.db.get_single_value( self.wip_warehouse = frappe.db.get_single_value(
"Manufacturing Settings", "default_wip_warehouse" "Manufacturing Settings", "default_wip_warehouse"
) )
@ -373,7 +373,7 @@ class WorkOrder(Document):
def on_cancel(self): def on_cancel(self):
self.validate_cancel() self.validate_cancel()
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
if self.production_plan and frappe.db.exists( if self.production_plan and frappe.db.exists(
"Production Plan Item Reference", {"parent": self.production_plan} "Production Plan Item Reference", {"parent": self.production_plan}

View File

@ -49,7 +49,7 @@ def make_custom_fields(update=True):
dict( dict(
fieldname="exempt_from_sales_tax", fieldname="exempt_from_sales_tax",
fieldtype="Check", fieldtype="Check",
insert_after="represents_company", insert_after="dn_required",
label="Is customer exempted from sales tax?", label="Is customer exempted from sales tax?",
) )
], ],

View File

@ -14,30 +14,35 @@
"naming_series", "naming_series",
"salutation", "salutation",
"customer_name", "customer_name",
"customer_type",
"customer_group",
"column_break0",
"territory",
"gender", "gender",
"default_bank_account",
"tax_id",
"tax_category",
"tax_withholding_category",
"lead_name", "lead_name",
"opportunity_name", "opportunity_name",
"image",
"column_break0",
"customer_group",
"customer_type",
"territory",
"account_manager", "account_manager",
"so_required", "image",
"dn_required", "defaults_tab",
"default_price_list",
"default_bank_account",
"column_break_14",
"default_currency",
"internal_customer_section",
"is_internal_customer", "is_internal_customer",
"represents_company", "represents_company",
"disabled", "column_break_70",
"allowed_to_transact_section",
"companies", "companies",
"currency_and_price_list", "more_info",
"default_currency", "market_segment",
"column_break_14", "industry",
"default_price_list", "customer_pos_id",
"website",
"language",
"column_break_45",
"customer_details",
"dashboard_tab",
"contact_and_address_tab",
"address_contacts", "address_contacts",
"address_html", "address_html",
"column_break1", "column_break1",
@ -49,34 +54,39 @@
"column_break_26", "column_break_26",
"customer_primary_address", "customer_primary_address",
"primary_address", "primary_address",
"default_receivable_accounts", "tax_tab",
"accounts", "taxation_section",
"tax_id",
"column_break_21",
"tax_category",
"tax_withholding_category",
"accounting_tab",
"credit_limit_section", "credit_limit_section",
"payment_terms", "payment_terms",
"credit_limits", "credit_limits",
"more_info", "default_receivable_accounts",
"customer_details", "accounts",
"column_break_45", "loyalty_points_tab",
"market_segment",
"industry",
"website",
"language",
"is_frozen",
"column_break_38",
"loyalty_program", "loyalty_program",
"column_break_54",
"loyalty_program_tier", "loyalty_program_tier",
"sales_team_section_break", "sales_team_tab",
"default_sales_partner",
"default_commission_rate",
"sales_team_section",
"sales_team", "sales_team",
"customer_pos_id" "sales_team_section",
"default_sales_partner",
"column_break_66",
"default_commission_rate",
"settings_tab",
"so_required",
"dn_required",
"column_break_53",
"is_frozen",
"disabled"
], ],
"fields": [ "fields": [
{ {
"fieldname": "basic_info", "fieldname": "basic_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Name and Type",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-user" "options": "fa fa-user"
}, },
@ -215,12 +225,6 @@
"options": "Company", "options": "Company",
"unique": 1 "unique": 1
}, },
{
"depends_on": "represents_company",
"fieldname": "allowed_to_transact_section",
"fieldtype": "Section Break",
"label": "Allowed To Transact With"
},
{ {
"depends_on": "represents_company", "depends_on": "represents_company",
"fieldname": "companies", "fieldname": "companies",
@ -228,12 +232,6 @@
"label": "Allowed To Transact With", "label": "Allowed To Transact With",
"options": "Allowed To Transact With" "options": "Allowed To Transact With"
}, },
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List"
},
{ {
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
@ -295,7 +293,7 @@
"description": "Select, to make the customer searchable with these fields", "description": "Select, to make the customer searchable with these fields",
"fieldname": "primary_address_and_contact_detail", "fieldname": "primary_address_and_contact_detail",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Primary Address and Contact Detail" "label": "Primary Address and Contact"
}, },
{ {
"description": "Reselect, if the chosen contact is edited after save", "description": "Reselect, if the chosen contact is edited after save",
@ -334,20 +332,18 @@
"read_only": 1 "read_only": 1
}, },
{ {
"collapsible": 1,
"fieldname": "default_receivable_accounts", "fieldname": "default_receivable_accounts",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Default Receivable Accounts" "label": "Default Receivable Accounts"
}, },
{ {
"description": "Mention if non-standard receivable account", "description": "Mention if a non-standard receivable account",
"fieldname": "accounts", "fieldname": "accounts",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Accounts", "label": "Receivable Accounts",
"options": "Party Account" "options": "Party Account"
}, },
{ {
"collapsible": 1,
"fieldname": "credit_limit_section", "fieldname": "credit_limit_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Credit Limit and Payment Terms" "label": "Credit Limit and Payment Terms"
@ -397,12 +393,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Frozen" "label": "Is Frozen"
}, },
{
"collapsible": 1,
"fieldname": "column_break_38",
"fieldtype": "Section Break",
"label": "Loyalty Points"
},
{ {
"fieldname": "loyalty_program", "fieldname": "loyalty_program",
"fieldtype": "Link", "fieldtype": "Link",
@ -417,15 +407,6 @@
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{
"collapsible": 1,
"collapsible_depends_on": "default_sales_partner",
"fieldname": "sales_team_section_break",
"fieldtype": "Section Break",
"label": "Sales Partner and Commission",
"oldfieldtype": "Section Break",
"options": "fa fa-group"
},
{ {
"fieldname": "default_sales_partner", "fieldname": "default_sales_partner",
"fieldtype": "Link", "fieldtype": "Link",
@ -446,13 +427,12 @@
"collapsible": 1, "collapsible": 1,
"collapsible_depends_on": "sales_team", "collapsible_depends_on": "sales_team",
"fieldname": "sales_team_section", "fieldname": "sales_team_section",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"label": "Sales Team"
}, },
{ {
"fieldname": "sales_team", "fieldname": "sales_team",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Sales Team Details", "label": "Sales Team",
"oldfieldname": "sales_team", "oldfieldname": "sales_team",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Sales Team" "options": "Sales Team"
@ -498,6 +478,83 @@
"no_copy": 1, "no_copy": 1,
"options": "Opportunity", "options": "Opportunity",
"print_hide": 1 "print_hide": 1
},
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
},
{
"fieldname": "defaults_tab",
"fieldtype": "Section Break",
"label": "Defaults"
},
{
"fieldname": "settings_tab",
"fieldtype": "Tab Break",
"label": "Settings"
},
{
"collapsible": 1,
"collapsible_depends_on": "default_sales_partner",
"fieldname": "sales_team_tab",
"fieldtype": "Tab Break",
"label": "Sales Team",
"oldfieldtype": "Section Break",
"options": "fa fa-group"
},
{
"fieldname": "column_break_66",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"fieldname": "dashboard_tab",
"fieldtype": "Tab Break",
"label": "Dashboard",
"show_dashboard": 1
},
{
"fieldname": "column_break_53",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "loyalty_points_tab",
"fieldtype": "Section Break",
"label": "Loyalty Points"
},
{
"fieldname": "taxation_section",
"fieldtype": "Section Break"
},
{
"fieldname": "accounting_tab",
"fieldtype": "Tab Break",
"label": "Accounting"
},
{
"fieldname": "tax_tab",
"fieldtype": "Tab Break",
"label": "Tax"
},
{
"collapsible": 1,
"collapsible_depends_on": "is_internal_customer",
"fieldname": "internal_customer_section",
"fieldtype": "Section Break",
"label": "Internal Customer"
},
{
"fieldname": "column_break_70",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_54",
"fieldtype": "Column Break"
} }
], ],
"icon": "fa fa-user", "icon": "fa fa-user",
@ -511,7 +568,7 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2022-04-16 20:32:34.000304", "modified": "2022-11-08 15:52:34.462657",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Customer", "name": "Customer",

View File

@ -294,7 +294,7 @@ class Customer(TransactionBase):
def after_rename(self, olddn, newdn, merge=False): def after_rename(self, olddn, newdn, merge=False):
if frappe.defaults.get_global_default("cust_master_name") == "Customer Name": if frappe.defaults.get_global_default("cust_master_name") == "Customer Name":
frappe.db.set(self, "customer_name", newdn) self.db_set("customer_name", newdn)
def set_loyalty_program(self): def set_loyalty_program(self):
if self.loyalty_program: if self.loyalty_program:

View File

@ -12,7 +12,7 @@
], ],
"fields": [ "fields": [
{ {
"columns": 4, "columns": 3,
"fieldname": "credit_limit", "fieldname": "credit_limit",
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
@ -31,6 +31,7 @@
"options": "Company" "options": "Company"
}, },
{ {
"columns": 3,
"default": "0", "default": "0",
"fieldname": "bypass_credit_limit_check", "fieldname": "bypass_credit_limit_check",
"fieldtype": "Check", "fieldtype": "Check",
@ -40,7 +41,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2019-12-31 15:43:05.822328", "modified": "2022-11-08 15:19:13.927194",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Customer Credit Limit", "name": "Customer Credit Limit",
@ -48,5 +49,6 @@
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC",
"states": []
} }

View File

@ -87,13 +87,13 @@ class InstallationNote(TransactionBase):
frappe.throw(_("Please pull items from Delivery Note")) frappe.throw(_("Please pull items from Delivery Note"))
def on_update(self): def on_update(self):
frappe.db.set(self, "status", "Draft") self.db_set("status", "Draft")
def on_submit(self): def on_submit(self):
self.validate_serial_no() self.validate_serial_no()
self.update_prevdoc_status() self.update_prevdoc_status()
frappe.db.set(self, "status", "Submitted") self.db_set("status", "Submitted")
def on_cancel(self): def on_cancel(self):
self.update_prevdoc_status() self.update_prevdoc_status()
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")

View File

@ -119,10 +119,10 @@ class Quotation(SellingController):
if not (self.is_fully_ordered() or self.is_partially_ordered()): if not (self.is_fully_ordered() or self.is_partially_ordered()):
get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"]) get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons] lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
frappe.db.set(self, "status", "Lost") self.db_set("status", "Lost")
if detailed_reason: if detailed_reason:
frappe.db.set(self, "order_lost_reason", detailed_reason) self.db_set("order_lost_reason", detailed_reason)
for reason in lost_reasons_list: for reason in lost_reasons_list:
if reason.get("lost_reason") in lost_reasons_lst: if reason.get("lost_reason") in lost_reasons_lst:
@ -247,7 +247,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
"Quotation": {"doctype": "Sales Order", "validation": {"docstatus": ["=", 1]}}, "Quotation": {"doctype": "Sales Order", "validation": {"docstatus": ["=", 1]}},
"Quotation Item": { "Quotation Item": {
"doctype": "Sales Order Item", "doctype": "Sales Order Item",
"field_map": {"parent": "prevdoc_docname"}, "field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: doc.qty > 0, "condition": lambda doc: doc.qty > 0,
}, },

View File

@ -193,6 +193,9 @@ class SalesOrder(SellingController):
{"Quotation": {"ref_dn_field": "prevdoc_docname", "compare_fields": [["company", "="]]}} {"Quotation": {"ref_dn_field": "prevdoc_docname", "compare_fields": [["company", "="]]}}
) )
if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")):
self.validate_rate_with_reference_doc([["Quotation", "prev_docname", "quotation_item"]])
def update_enquiry_status(self, prevdoc, flag): def update_enquiry_status(self, prevdoc, flag):
enq = frappe.db.sql( enq = frappe.db.sql(
"select t2.prevdoc_docname from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.name=%s", "select t2.prevdoc_docname from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.name=%s",
@ -246,7 +249,7 @@ class SalesOrder(SellingController):
self.update_project() self.update_project()
self.update_prevdoc_status("cancel") self.update_prevdoc_status("cancel")
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
self.update_blanket_order() self.update_blanket_order()

View File

@ -70,6 +70,7 @@
"warehouse", "warehouse",
"target_warehouse", "target_warehouse",
"prevdoc_docname", "prevdoc_docname",
"quotation_item",
"col_break4", "col_break4",
"against_blanket_order", "against_blanket_order",
"blanket_order", "blanket_order",
@ -838,12 +839,20 @@
"label": "Purchase Order Item", "label": "Purchase Order Item",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "quotation_item",
"fieldtype": "Data",
"hidden": 1,
"label": "quotation_item",
"no_copy": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-10-26 16:05:02.712705", "modified": "2022-11-10 18:20:30.137455",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order Item", "name": "Sales Order Item",

View File

@ -207,15 +207,14 @@ class Company(NestedSet):
frappe.local.flags.ignore_root_company_validation = True frappe.local.flags.ignore_root_company_validation = True
create_charts(self.name, self.chart_of_accounts, self.existing_company) create_charts(self.name, self.chart_of_accounts, self.existing_company)
frappe.db.set( self.db_set(
self,
"default_receivable_account", "default_receivable_account",
frappe.db.get_value( frappe.db.get_value(
"Account", {"company": self.name, "account_type": "Receivable", "is_group": 0} "Account", {"company": self.name, "account_type": "Receivable", "is_group": 0}
), ),
) )
frappe.db.set(
self, self.db_set(
"default_payable_account", "default_payable_account",
frappe.db.get_value( frappe.db.get_value(
"Account", {"company": self.name, "account_type": "Payable", "is_group": 0} "Account", {"company": self.name, "account_type": "Payable", "is_group": 0}
@ -491,12 +490,12 @@ class Company(NestedSet):
cc_doc.flags.ignore_mandatory = True cc_doc.flags.ignore_mandatory = True
cc_doc.insert() cc_doc.insert()
frappe.db.set(self, "cost_center", _("Main") + " - " + self.abbr) self.db_set("cost_center", _("Main") + " - " + self.abbr)
frappe.db.set(self, "round_off_cost_center", _("Main") + " - " + self.abbr) self.db_set("round_off_cost_center", _("Main") + " - " + self.abbr)
frappe.db.set(self, "depreciation_cost_center", _("Main") + " - " + self.abbr) self.db_set("depreciation_cost_center", _("Main") + " - " + self.abbr)
def after_rename(self, olddn, newdn, merge=False): def after_rename(self, olddn, newdn, merge=False):
frappe.db.set(self, "company_name", newdn) self.db_set("company_name", newdn)
frappe.db.sql( frappe.db.sql(
"""update `tabDefaultValue` set defvalue=%s """update `tabDefaultValue` set defvalue=%s

View File

@ -120,7 +120,6 @@ class MaterialRequest(BuyingController):
self.title = _("{0} Request for {1}").format(self.material_request_type, items)[:100] self.title = _("{0} Request for {1}").format(self.material_request_type, items)[:100]
def on_submit(self): def on_submit(self):
# frappe.db.set(self, 'status', 'Submitted')
self.update_requested_qty() self.update_requested_qty()
self.update_requested_qty_in_production_plan() self.update_requested_qty_in_production_plan()
if self.material_request_type == "Purchase": if self.material_request_type == "Purchase":

View File

@ -216,7 +216,7 @@ class TestMaterialRequest(FrappeTestCase):
po.load_from_db() po.load_from_db()
mr.update_status("Stopped") mr.update_status("Stopped")
self.assertRaises(frappe.InvalidStatusError, po.submit) self.assertRaises(frappe.InvalidStatusError, po.submit)
frappe.db.set(po, "docstatus", 1) po.db_set("docstatus", 1)
self.assertRaises(frappe.InvalidStatusError, po.cancel) self.assertRaises(frappe.InvalidStatusError, po.cancel)
# resubmit and check for per complete # resubmit and check for per complete

View File

@ -6,7 +6,9 @@ import frappe
from frappe import _, throw from frappe import _, throw
from frappe.desk.notifications import clear_doctype_notifications from frappe.desk.notifications import clear_doctype_notifications
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import CombineDatetime
from frappe.utils import cint, flt, getdate, nowdate from frappe.utils import cint, flt, getdate, nowdate
from pypika import functions as fn
import erpnext import erpnext
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
@ -750,48 +752,38 @@ class PurchaseReceipt(BuyingController):
def update_billing_status(self, update_modified=True): def update_billing_status(self, update_modified=True):
updated_pr = [self.name] updated_pr = [self.name]
po_details = []
for d in self.get("items"): for d in self.get("items"):
if d.get("purchase_invoice") and d.get("purchase_invoice_item"): if d.get("purchase_invoice") and d.get("purchase_invoice_item"):
d.db_set("billed_amt", d.amount, update_modified=update_modified) d.db_set("billed_amt", d.amount, update_modified=update_modified)
elif d.purchase_order_item: elif d.purchase_order_item:
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified) po_details.append(d.purchase_order_item)
if po_details:
updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
for pr in set(updated_pr): for pr in set(updated_pr):
pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) pr_doc = self if (pr == self.name) else frappe.get_cached_doc("Purchase Receipt", pr)
update_billing_percentage(pr_doc, update_modified=update_modified) update_billing_percentage(pr_doc, update_modified=update_modified)
self.load_from_db() self.load_from_db()
def update_billed_amount_based_on_po(po_detail, update_modified=True): def update_billed_amount_based_on_po(po_details, update_modified=True):
# Billed against Sales Order directly po_billed_amt_details = get_billed_amount_against_po(po_details)
billed_against_po = frappe.db.sql(
"""select sum(amount) from `tabPurchase Invoice Item`
where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""",
po_detail,
)
billed_against_po = billed_against_po and billed_against_po[0][0] or 0
# Get all Purchase Receipt Item rows against the Purchase Order Item row # Get all Purchase Receipt Item rows against the Purchase Order Items
pr_details = frappe.db.sql( pr_details = get_purchase_receipts_against_po_details(po_details)
"""select pr_item.name, pr_item.amount, pr_item.parent
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr pr_items = [pr_detail.name for pr_detail in pr_details]
where pr.name=pr_item.parent and pr_item.purchase_order_item=%s pr_items_billed_amount = get_billed_amount_against_pr(pr_items)
and pr.docstatus=1 and pr.is_return = 0
order by pr.posting_date asc, pr.posting_time asc, pr.name asc""",
po_detail,
as_dict=1,
)
updated_pr = [] updated_pr = []
for pr_item in pr_details: for pr_item in pr_details:
billed_against_po = flt(po_billed_amt_details.get(pr_item.purchase_order_item))
# Get billed amount directly against Purchase Receipt # Get billed amount directly against Purchase Receipt
billed_amt_agianst_pr = frappe.db.sql( billed_amt_agianst_pr = flt(pr_items_billed_amount.get(pr_item.name, 0))
"""select sum(amount) from `tabPurchase Invoice Item`
where pr_detail=%s and docstatus=1""",
pr_item.name,
)
billed_amt_agianst_pr = billed_amt_agianst_pr and billed_amt_agianst_pr[0][0] or 0
# Distribute billed amount directly against PO between PRs based on FIFO # Distribute billed amount directly against PO between PRs based on FIFO
if billed_against_po and billed_amt_agianst_pr < pr_item.amount: if billed_against_po and billed_amt_agianst_pr < pr_item.amount:
@ -803,19 +795,90 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
billed_amt_agianst_pr += billed_against_po billed_amt_agianst_pr += billed_against_po
billed_against_po = 0 billed_against_po = 0
frappe.db.set_value( po_billed_amt_details[pr_item.purchase_order_item] = billed_against_po
"Purchase Receipt Item",
pr_item.name,
"billed_amt",
billed_amt_agianst_pr,
update_modified=update_modified,
)
updated_pr.append(pr_item.parent) if pr_item.billed_amt != billed_amt_agianst_pr:
frappe.db.set_value(
"Purchase Receipt Item",
pr_item.name,
"billed_amt",
billed_amt_agianst_pr,
update_modified=update_modified,
)
updated_pr.append(pr_item.parent)
return updated_pr return updated_pr
def get_purchase_receipts_against_po_details(po_details):
# Get Purchase Receipts against Purchase Order Items
purchase_receipt = frappe.qb.DocType("Purchase Receipt")
purchase_receipt_item = frappe.qb.DocType("Purchase Receipt Item")
query = (
frappe.qb.from_(purchase_receipt)
.inner_join(purchase_receipt_item)
.on(purchase_receipt.name == purchase_receipt_item.parent)
.select(
purchase_receipt_item.name,
purchase_receipt_item.parent,
purchase_receipt_item.amount,
purchase_receipt_item.billed_amt,
purchase_receipt_item.purchase_order_item,
)
.where(
(purchase_receipt_item.purchase_order_item.isin(po_details))
& (purchase_receipt.docstatus == 1)
& (purchase_receipt.is_return == 0)
)
.orderby(CombineDatetime(purchase_receipt.posting_date, purchase_receipt.posting_time))
.orderby(purchase_receipt.name)
)
return query.run(as_dict=True)
def get_billed_amount_against_pr(pr_items):
# Get billed amount directly against Purchase Receipt
if not pr_items:
return {}
purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(purchase_invoice_item)
.select(fn.Sum(purchase_invoice_item.amount).as_("billed_amt"), purchase_invoice_item.pr_detail)
.where((purchase_invoice_item.pr_detail.isin(pr_items)) & (purchase_invoice_item.docstatus == 1))
.groupby(purchase_invoice_item.pr_detail)
).run(as_dict=1)
return {d.pr_detail: flt(d.billed_amt) for d in query}
def get_billed_amount_against_po(po_items):
# Get billed amount directly against Purchase Order
if not po_items:
return {}
purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(purchase_invoice_item)
.select(fn.Sum(purchase_invoice_item.amount).as_("billed_amt"), purchase_invoice_item.po_detail)
.where(
(purchase_invoice_item.po_detail.isin(po_items))
& (purchase_invoice_item.docstatus == 1)
& (purchase_invoice_item.pr_detail.isnull())
)
.groupby(purchase_invoice_item.po_detail)
).run(as_dict=1)
return {d.po_detail: flt(d.billed_amt) for d in query}
def update_billing_percentage(pr_doc, update_modified=True): def update_billing_percentage(pr_doc, update_modified=True):
# Reload as billed amount was set in db directly # Reload as billed amount was set in db directly
pr_doc.load_from_db() pr_doc.load_from_db()

View File

@ -330,6 +330,9 @@ def get_basic_details(args, item, overwrite_warehouse=True):
else: else:
args.uom = item.stock_uom args.uom = item.stock_uom
# Set stock UOM in args, so that it can be used while fetching item price
args.stock_uom = item.stock_uom
if args.get("batch_no") and item.name != frappe.get_cached_value( if args.get("batch_no") and item.name != frappe.get_cached_value(
"Batch", args.get("batch_no"), "item" "Batch", args.get("batch_no"), "item"
): ):

View File

@ -35,7 +35,7 @@ class WarrantyClaim(TransactionBase):
lst1 = ",".join(x[0] for x in lst) lst1 = ",".join(x[0] for x in lst)
frappe.throw(_("Cancel Material Visit {0} before cancelling this Warranty Claim").format(lst1)) frappe.throw(_("Cancel Material Visit {0} before cancelling this Warranty Claim").format(lst1))
else: else:
frappe.db.set(self, "status", "Cancelled") self.db_set("status", "Cancelled")
def on_update(self): def on_update(self):
pass pass