style: bulk format code with black

v13 port because otherwise backports will result in conflicts always
This commit is contained in:
Ankush Menat
2022-03-29 17:29:34 +05:30
parent 7cc84dcbb4
commit c07713b860
1555 changed files with 96709 additions and 66138 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -20,12 +20,11 @@ from erpnext.stock.utils import get_incoming_rate
class QtyMismatchError(ValidationError):
pass
class BuyingController(StockController, Subcontracting):
class BuyingController(StockController, Subcontracting):
def get_feed(self):
if self.get("supplier_name"):
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency,
self.grand_total)
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
def validate(self):
super(BuyingController, self).validate()
@@ -40,16 +39,18 @@ class BuyingController(StockController, Subcontracting):
self.set_supplier_address()
self.validate_asset_return()
if self.doctype=="Purchase Invoice":
if self.doctype == "Purchase Invoice":
self.validate_purchase_receipt_if_update_stock()
if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and self.update_stock):
if self.doctype == "Purchase Receipt" or (
self.doctype == "Purchase Invoice" and self.update_stock
):
# self.validate_purchase_return()
self.validate_rejected_warehouse()
self.validate_accepted_rejected_qty()
validate_for_items(self)
#sub-contracting
# sub-contracting
self.validate_for_subcontracting()
self.create_raw_materials_supplied("supplied_items")
self.set_landed_cost_voucher_amount()
@@ -59,8 +60,12 @@ class BuyingController(StockController, Subcontracting):
def onload(self):
super(BuyingController, self).onload()
self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings',
'backflush_raw_materials_of_subcontract_based_on'))
self.set_onload(
"backflush_based_on",
frappe.db.get_single_value(
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
),
)
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@@ -77,9 +82,9 @@ class BuyingController(StockController, Subcontracting):
doctype=self.doctype,
company=self.company,
party_address=self.get("supplier_address"),
shipping_address=self.get('shipping_address'),
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'),
ignore_permissions=self.flags.ignore_permissions
shipping_address=self.get("shipping_address"),
fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
ignore_permissions=self.flags.ignore_permissions,
)
)
@@ -88,14 +93,16 @@ class BuyingController(StockController, Subcontracting):
def set_supplier_from_item_default(self):
if self.meta.get_field("supplier") and not self.supplier:
for d in self.get("items"):
supplier = frappe.db.get_value("Item Default",
{"parent": d.item_code, "company": self.company}, "default_supplier")
supplier = frappe.db.get_value(
"Item Default", {"parent": d.item_code, "company": self.company}, "default_supplier"
)
if supplier:
self.supplier = supplier
else:
item_group = frappe.db.get_value("Item", d.item_code, "item_group")
supplier = frappe.db.get_value("Item Default",
{"parent": item_group, "company": self.company}, "default_supplier")
supplier = frappe.db.get_value(
"Item Default", {"parent": item_group, "company": self.company}, "default_supplier"
)
if supplier:
self.supplier = supplier
break
@@ -106,55 +113,71 @@ class BuyingController(StockController, Subcontracting):
self.update_tax_category(msg)
def update_tax_category(self, msg):
tax_for_valuation = [d for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]]
tax_for_valuation = [
d for d in self.get("taxes") if d.category in ["Valuation", "Valuation and Total"]
]
if tax_for_valuation:
for d in tax_for_valuation:
d.category = 'Total'
d.category = "Total"
msgprint(msg)
def validate_asset_return(self):
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
if self.doctype not in ["Purchase Receipt", "Purchase Invoice"] or not self.is_return:
return
purchase_doc_field = 'purchase_receipt' if self.doctype == 'Purchase Receipt' else 'purchase_invoice'
not_cancelled_asset = [d.name for d in frappe.db.get_all("Asset", {
purchase_doc_field: self.return_against,
"docstatus": 1
})]
purchase_doc_field = (
"purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice"
)
not_cancelled_asset = [
d.name
for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1})
]
if self.is_return and len(not_cancelled_asset):
frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.")
.format(self.return_against), title=_("Not Allowed"))
frappe.throw(
_(
"{} has submitted assets linked to it. You need to cancel the assets to create purchase return."
).format(self.return_against),
title=_("Not Allowed"),
)
def get_asset_items(self):
if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
if self.doctype not in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]:
return []
return [d.item_code for d in self.items if d.is_fixed_asset]
def set_landed_cost_voucher_amount(self):
for d in self.get("items"):
lc_voucher_data = frappe.db.sql("""select sum(applicable_charges), cost_center
lc_voucher_data = frappe.db.sql(
"""select sum(applicable_charges), cost_center
from `tabLanded Cost Item`
where docstatus = 1 and purchase_receipt_item = %s""", d.name)
where docstatus = 1 and purchase_receipt_item = %s""",
d.name,
)
d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
d.db_set('cost_center', lc_voucher_data[0][1])
d.db_set("cost_center", lc_voucher_data[0][1])
def validate_from_warehouse(self):
for item in self.get('items'):
if item.get('from_warehouse') and (item.get('from_warehouse') == item.get('warehouse')):
frappe.throw(_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx))
for item in self.get("items"):
if item.get("from_warehouse") and (item.get("from_warehouse") == item.get("warehouse")):
frappe.throw(
_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx)
)
if item.get('from_warehouse') and self.get('is_subcontracted') == 'Yes':
frappe.throw(_("Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor").format(item.idx))
if item.get("from_warehouse") and self.get("is_subcontracted") == "Yes":
frappe.throw(
_(
"Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor"
).format(item.idx)
)
def set_supplier_address(self):
address_dict = {
'supplier_address': 'address_display',
'shipping_address': 'shipping_address_display'
"supplier_address": "address_display",
"shipping_address": "shipping_address_display",
}
for address_field, address_display_field in address_dict.items():
@@ -163,6 +186,7 @@ class BuyingController(StockController, Subcontracting):
def set_total_in_words(self):
from frappe.utils import money_in_words
if self.meta.get_field("base_in_words"):
if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled():
amount = self.base_rounded_total
@@ -181,10 +205,10 @@ class BuyingController(StockController, Subcontracting):
# update valuation rate
def update_valuation_rate(self, reset_outgoing_rate=True):
"""
item_tax_amount is the total tax amount applied on that item
stored for valuation
item_tax_amount is the total tax amount applied on that item
stored for valuation
TODO: rename item_tax_amount to valuation_tax_amount
TODO: rename item_tax_amount to valuation_tax_amount
"""
stock_and_asset_items = []
stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
@@ -192,36 +216,50 @@ class BuyingController(StockController, Subcontracting):
stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
last_item_idx = 1
for d in self.get("items"):
if (d.item_code and d.item_code in stock_and_asset_items):
if d.item_code and d.item_code in stock_and_asset_items:
stock_and_asset_items_qty += flt(d.qty)
stock_and_asset_items_amount += flt(d.base_net_amount)
last_item_idx = d.idx
total_valuation_amount = sum(flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"])
total_valuation_amount = sum(
flt(d.base_tax_amount_after_discount_amount)
for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]
)
valuation_amount_adjustment = total_valuation_amount
for i, item in enumerate(self.get("items")):
if item.item_code and item.qty and item.item_code in stock_and_asset_items:
item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
item_proportion = (
flt(item.base_net_amount) / stock_and_asset_items_amount
if stock_and_asset_items_amount
else flt(item.qty) / stock_and_asset_items_qty
)
if i == (last_item_idx - 1):
item.item_tax_amount = flt(valuation_amount_adjustment,
self.precision("item_tax_amount", item))
item.item_tax_amount = flt(
valuation_amount_adjustment, self.precision("item_tax_amount", item)
)
else:
item.item_tax_amount = flt(item_proportion * total_valuation_amount,
self.precision("item_tax_amount", item))
item.item_tax_amount = flt(
item_proportion * total_valuation_amount, self.precision("item_tax_amount", item)
)
valuation_amount_adjustment -= item.item_tax_amount
self.round_floats_in(item)
if flt(item.conversion_factor)==0.0:
item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
if flt(item.conversion_factor) == 0.0:
item.conversion_factor = (
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
)
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + item.rm_supp_cost
+ flt(item.landed_cost_voucher_amount)) / qty_in_stock_uom)
item.valuation_rate = (
item.base_net_amount
+ item.item_tax_amount
+ item.rm_supp_cost
+ flt(item.landed_cost_voucher_amount)
) / qty_in_stock_uom
else:
item.valuation_rate = 0.0
@@ -242,44 +280,53 @@ class BuyingController(StockController, Subcontracting):
# Get outgoing rate based on original item cost based on valuation method
if not d.get(frappe.scrub(ref_doctype)):
outgoing_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.get('from_warehouse'),
"posting_date": self.get('posting_date') or self.get('transation_date'),
"posting_time": self.get('posting_time'),
"qty": -1 * flt(d.get('stock_qty')),
"serial_no": d.get('serial_no'),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation")
}, raise_error_if_no_rate=False)
outgoing_rate = get_incoming_rate(
{
"item_code": d.item_code,
"warehouse": d.get("from_warehouse"),
"posting_date": self.get("posting_date") or self.get("transation_date"),
"posting_time": self.get("posting_time"),
"qty": -1 * flt(d.get("stock_qty")),
"serial_no": d.get("serial_no"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
},
raise_error_if_no_rate=False,
)
rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
rate = flt(outgoing_rate * d.conversion_factor, d.precision("rate"))
else:
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
if self.is_internal_transfer():
if rate != d.rate:
d.rate = rate
d.discount_percentage = 0
d.discount_amount = 0
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
.format(d.idx), alert=1)
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
supplied_items_cost = 0.0
for d in self.get("supplied_items"):
if d.reference_name == item_row_id:
if reset_outgoing_rate and frappe.get_cached_value('Item', d.rm_item_code, 'is_stock_item'):
rate = get_incoming_rate({
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * d.consumed_qty,
"serial_no": d.serial_no
})
if reset_outgoing_rate and frappe.get_cached_value("Item", d.rm_item_code, "is_stock_item"):
rate = get_incoming_rate(
{
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * d.consumed_qty,
"serial_no": d.serial_no,
}
)
if rate > 0:
d.rate = rate
@@ -314,7 +361,7 @@ class BuyingController(StockController, Subcontracting):
item.bom = None
def create_raw_materials_supplied(self, raw_material_table):
if self.is_subcontracted=="Yes":
if self.is_subcontracted == "Yes":
self.set_materials_for_subcontracted_items(raw_material_table)
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@@ -322,19 +369,17 @@ class BuyingController(StockController, Subcontracting):
item.rm_supp_cost = 0.0
if self.is_subcontracted == "No" and self.get("supplied_items"):
self.set('supplied_items', [])
self.set("supplied_items", [])
@property
def sub_contracted_items(self):
if not hasattr(self, "_sub_contracted_items"):
self._sub_contracted_items = []
item_codes = list(set(item.item_code for item in
self.get("items")))
item_codes = list(set(item.item_code for item in self.get("items")))
if item_codes:
items = frappe.get_all('Item', filters={
'name': ['in', item_codes],
'is_sub_contracted_item': 1
})
items = frappe.get_all(
"Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1}
)
self._sub_contracted_items = [item.name for item in items]
return self._sub_contracted_items
@@ -348,9 +393,11 @@ class BuyingController(StockController, Subcontracting):
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
if self.doctype == "Purchase Receipt" and d.meta.get_field("received_stock_qty"):
# Set Received Qty in Stock UOM
d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
d.received_stock_qty = flt(d.received_qty) * flt(
d.conversion_factor, d.precision("conversion_factor")
)
def validate_purchase_return(self):
for d in self.get("items"):
@@ -366,20 +413,26 @@ class BuyingController(StockController, Subcontracting):
d.rejected_warehouse = self.rejected_warehouse
if not d.rejected_warehouse:
frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code))
frappe.throw(
_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(
d.idx, d.item_code
)
)
# validate accepted and rejected qty
def validate_accepted_rejected_qty(self):
for d in self.get("items"):
self.validate_negative_quantity(d, ["received_qty","qty", "rejected_qty"])
self.validate_negative_quantity(d, ["received_qty", "qty", "rejected_qty"])
if not flt(d.received_qty) and (flt(d.qty) or flt(d.rejected_qty)):
d.received_qty = flt(d.qty) + flt(d.rejected_qty)
# Check Received Qty = Accepted Qty + Rejected Qty
val = flt(d.qty) + flt(d.rejected_qty)
if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))):
message = _("Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}").format(d.idx, d.item_code)
if flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty")):
message = _(
"Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}"
).format(d.idx, d.item_code)
frappe.throw(msg=message, title=_("Mismatch"), exc=QtyMismatchError)
def validate_negative_quantity(self, item_row, field_list):
@@ -389,15 +442,20 @@ class BuyingController(StockController, Subcontracting):
item_row = item_row.as_dict()
for fieldname in field_list:
if flt(item_row[fieldname]) < 0:
frappe.throw(_("Row #{0}: {1} can not be negative for item {2}").format(item_row['idx'],
frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))
frappe.throw(
_("Row #{0}: {1} can not be negative for item {2}").format(
item_row["idx"],
frappe.get_meta(item_row.doctype).get_label(fieldname),
item_row["item_code"],
)
)
def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname):
for d in self.get("items"):
if d.get(ref_fieldname):
status = frappe.db.get_value(ref_doctype, d.get(ref_fieldname), "status")
if status in ("Closed", "On Hold"):
frappe.throw(_("{0} {1} is {2}").format(ref_doctype,d.get(ref_fieldname), status))
frappe.throw(_("{0} {1} is {2}").format(ref_doctype, d.get(ref_fieldname), status))
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
self.update_ordered_and_reserved_qty()
@@ -405,76 +463,88 @@ class BuyingController(StockController, Subcontracting):
sl_entries = []
stock_items = self.get_stock_items()
for d in self.get('items'):
for d in self.get("items"):
if d.item_code in stock_items and d.warehouse:
pr_qty = flt(d.qty) * flt(d.conversion_factor)
if pr_qty:
if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==1)
or (cint(self.is_return) and self.docstatus==2)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse,
"outgoing_rate": d.rate,
"recalculate_rate": 1,
"dependant_sle_voucher_detail_no": d.name
})
if d.from_warehouse and (
(not cint(self.is_return) and self.docstatus == 1)
or (cint(self.is_return) and self.docstatus == 2)
):
from_warehouse_sle = self.get_sl_entries(
d,
{
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse,
"outgoing_rate": d.rate,
"recalculate_rate": 1,
"dependant_sle_voucher_detail_no": d.name,
},
)
sl_entries.append(from_warehouse_sle)
sle = self.get_sl_entries(d, {
"actual_qty": flt(pr_qty),
"serial_no": cstr(d.serial_no).strip()
})
sle = self.get_sl_entries(
d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
)
if self.is_return:
outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
sle.update({
"outgoing_rate": outgoing_rate,
"recalculate_rate": 1
})
sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1})
if d.from_warehouse:
sle.dependant_sle_voucher_detail_no = d.name
else:
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
sle.update({
"incoming_rate": incoming_rate,
"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0
})
sle.update(
{
"incoming_rate": incoming_rate,
"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0,
}
)
sl_entries.append(sle)
if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==2)
or (cint(self.is_return) and self.docstatus==1)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse,
"recalculate_rate": 1
})
if d.from_warehouse and (
(not cint(self.is_return) and self.docstatus == 2)
or (cint(self.is_return) and self.docstatus == 1)
):
from_warehouse_sle = self.get_sl_entries(
d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1}
)
sl_entries.append(from_warehouse_sle)
if flt(d.rejected_qty) != 0:
sl_entries.append(self.get_sl_entries(d, {
"warehouse": d.rejected_warehouse,
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
"serial_no": cstr(d.rejected_serial_no).strip(),
"incoming_rate": 0.0
}))
sl_entries.append(
self.get_sl_entries(
d,
{
"warehouse": d.rejected_warehouse,
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
"serial_no": cstr(d.rejected_serial_no).strip(),
"incoming_rate": 0.0,
},
)
)
self.make_sl_entries_for_supplier_warehouse(sl_entries)
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher)
self.make_sl_entries(
sl_entries,
allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher,
)
def update_ordered_and_reserved_qty(self):
po_map = {}
for d in self.get("items"):
if self.doctype=="Purchase Receipt" \
and d.purchase_order:
po_map.setdefault(d.purchase_order, []).append(d.purchase_order_item)
if self.doctype == "Purchase Receipt" and d.purchase_order:
po_map.setdefault(d.purchase_order, []).append(d.purchase_order_item)
elif self.doctype=="Purchase Invoice" and d.purchase_order and d.po_detail:
elif self.doctype == "Purchase Invoice" and d.purchase_order and d.po_detail:
po_map.setdefault(d.purchase_order, []).append(d.po_detail)
for po, po_item_rows in po_map.items():
@@ -482,68 +552,78 @@ class BuyingController(StockController, Subcontracting):
po_obj = frappe.get_doc("Purchase Order", po)
if po_obj.status in ["Closed", "Cancelled"]:
frappe.throw(_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
frappe.InvalidStatusError)
frappe.throw(
_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
frappe.InvalidStatusError,
)
po_obj.update_ordered_qty(po_item_rows)
if self.is_subcontracted:
po_obj.update_reserved_qty_for_subcontract()
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
if hasattr(self, 'supplied_items'):
for d in self.get('supplied_items'):
if hasattr(self, "supplied_items"):
for d in self.get("supplied_items"):
# negative quantity is passed, as raw material qty has to be decreased
# when PR is submitted and it has to be increased when PR is cancelled
sl_entries.append(self.get_sl_entries(d, {
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"actual_qty": -1*flt(d.consumed_qty),
"dependant_sle_voucher_detail_no": d.reference_name
}))
sl_entries.append(
self.get_sl_entries(
d,
{
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"actual_qty": -1 * flt(d.consumed_qty),
"dependant_sle_voucher_detail_no": d.reference_name,
},
)
)
def on_submit(self):
if self.get('is_return'):
if self.get("is_return"):
return
if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt"
self.process_fixed_asset()
self.update_fixed_asset(field)
if self.doctype in ['Purchase Order', 'Purchase Receipt']:
update_last_purchase_rate(self, is_submit = 1)
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
update_last_purchase_rate(self, is_submit=1)
def on_cancel(self):
super(BuyingController, self).on_cancel()
if self.get('is_return'):
if self.get("is_return"):
return
if self.doctype in ['Purchase Order', 'Purchase Receipt']:
update_last_purchase_rate(self, is_submit = 0)
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
update_last_purchase_rate(self, is_submit=0)
if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt"
self.delete_linked_asset()
self.update_fixed_asset(field, delete_asset=True)
def validate_budget(self):
if self.docstatus == 1:
for data in self.get('items'):
for data in self.get("items"):
args = data.as_dict()
args.update({
'doctype': self.doctype,
'company': self.company,
'posting_date': (self.schedule_date
if self.doctype == 'Material Request' else self.transaction_date)
})
args.update(
{
"doctype": self.doctype,
"company": self.company,
"posting_date": (
self.schedule_date if self.doctype == "Material Request" else self.transaction_date
),
}
)
validate_expense_against_budget(args)
def process_fixed_asset(self):
if self.doctype == 'Purchase Invoice' and not self.update_stock:
if self.doctype == "Purchase Invoice" and not self.update_stock:
return
asset_items = self.get_asset_items()
@@ -558,10 +638,10 @@ class BuyingController(StockController, Subcontracting):
if d.is_fixed_asset:
item_data = items_data.get(d.item_code)
if item_data.get('auto_create_assets'):
if item_data.get("auto_create_assets"):
# If asset has to be auto created
# Check for asset naming series
if item_data.get('asset_naming_series'):
if item_data.get("asset_naming_series"):
created_assets = []
for qty in range(cint(d.qty)):
@@ -570,21 +650,31 @@ class BuyingController(StockController, Subcontracting):
if len(created_assets) > 5:
# dont show asset form links if more than 5 assets are created
messages.append(_('{} Assets created for {}').format(len(created_assets), frappe.bold(d.item_code)))
else:
assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets))
assets_link = frappe.bold(','.join(assets_link))
is_plural = 's' if len(created_assets) != 1 else ''
messages.append(
_('Asset{} {assets_link} created for {}').format(is_plural, frappe.bold(d.item_code), assets_link=assets_link)
_("{} Assets created for {}").format(len(created_assets), frappe.bold(d.item_code))
)
else:
assets_link = list(map(lambda d: frappe.utils.get_link_to_form("Asset", d), created_assets))
assets_link = frappe.bold(",".join(assets_link))
is_plural = "s" if len(created_assets) != 1 else ""
messages.append(
_("Asset{} {assets_link} created for {}").format(
is_plural, frappe.bold(d.item_code), assets_link=assets_link
)
)
else:
frappe.throw(_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}")
.format(d.idx, frappe.bold(d.item_code)))
frappe.throw(
_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}").format(
d.idx, frappe.bold(d.item_code)
)
)
else:
messages.append(_("Assets not created for {0}. You will have to create asset manually.")
.format(frappe.bold(d.item_code)))
messages.append(
_("Assets not created for {0}. You will have to create asset manually.").format(
frappe.bold(d.item_code)
)
)
for message in messages:
frappe.msgprint(message, title="Success", indicator="green")
@@ -593,26 +683,29 @@ class BuyingController(StockController, Subcontracting):
if not row.asset_location:
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
item_data = frappe.db.get_value('Item',
row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1)
item_data = frappe.db.get_value(
"Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
)
purchase_amount = flt(row.base_rate + row.item_tax_amount)
asset = frappe.get_doc({
'doctype': 'Asset',
'item_code': row.item_code,
'asset_name': row.item_name,
'naming_series': item_data.get('asset_naming_series') or 'AST',
'asset_category': item_data.get('asset_category'),
'location': row.asset_location,
'company': self.company,
'supplier': self.supplier,
'purchase_date': self.posting_date,
'calculate_depreciation': 1,
'purchase_receipt_amount': purchase_amount,
'gross_purchase_amount': purchase_amount,
'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None,
'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None
})
asset = frappe.get_doc(
{
"doctype": "Asset",
"item_code": row.item_code,
"asset_name": row.item_name,
"naming_series": item_data.get("asset_naming_series") or "AST",
"asset_category": item_data.get("asset_category"),
"location": row.asset_location,
"company": self.company,
"supplier": self.supplier,
"purchase_date": self.posting_date,
"calculate_depreciation": 1,
"purchase_receipt_amount": purchase_amount,
"gross_purchase_amount": purchase_amount,
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
}
)
asset.flags.ignore_validate = True
asset.flags.ignore_mandatory = True
@@ -621,22 +714,25 @@ class BuyingController(StockController, Subcontracting):
return asset.name
def update_fixed_asset(self, field, delete_asset = False):
def update_fixed_asset(self, field, delete_asset=False):
for d in self.get("items"):
if d.is_fixed_asset:
is_auto_create_enabled = frappe.db.get_value('Item', d.item_code, 'auto_create_assets')
assets = frappe.db.get_all('Asset', filters={ field : self.name, 'item_code' : d.item_code })
is_auto_create_enabled = frappe.db.get_value("Item", d.item_code, "auto_create_assets")
assets = frappe.db.get_all("Asset", filters={field: self.name, "item_code": d.item_code})
for asset in assets:
asset = frappe.get_doc('Asset', asset.name)
asset = frappe.get_doc("Asset", asset.name)
if delete_asset and is_auto_create_enabled:
# need to delete movements to delete assets otherwise throws link exists error
movements = frappe.db.sql(
"""SELECT asm.name
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1)
WHERE asm_item.parent=asm.name and asm_item.asset=%s""",
asset.name,
as_dict=1,
)
for movement in movements:
frappe.delete_doc('Asset Movement', movement.name, force=1)
frappe.delete_doc("Asset Movement", movement.name, force=1)
frappe.delete_doc("Asset", asset.name, force=1)
continue
@@ -649,8 +745,11 @@ class BuyingController(StockController, Subcontracting):
asset.set(field, None)
asset.supplier = None
if asset.docstatus == 1 and delete_asset:
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue.')
.format(frappe.utils.get_link_to_form('Asset', asset.name)))
frappe.throw(
_(
"Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue."
).format(frappe.utils.get_link_to_form("Asset", asset.name))
)
asset.flags.ignore_validate_update_after_submit = True
asset.flags.ignore_mandatory = True
@@ -660,7 +759,7 @@ class BuyingController(StockController, Subcontracting):
asset.save()
def delete_linked_asset(self):
if self.doctype == 'Purchase Invoice' and not self.get('update_stock'):
if self.doctype == "Purchase Invoice" and not self.get("update_stock"):
return
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
@@ -671,37 +770,47 @@ class BuyingController(StockController, Subcontracting):
if any(d.schedule_date for d in self.get("items")):
# Select earliest schedule_date.
self.schedule_date = min(d.schedule_date for d in self.get("items")
if d.schedule_date is not None)
self.schedule_date = min(
d.schedule_date for d in self.get("items") if d.schedule_date is not None
)
if self.schedule_date:
for d in self.get('items'):
for d in self.get("items"):
if not d.schedule_date:
d.schedule_date = self.schedule_date
if (d.schedule_date and self.transaction_date and
getdate(d.schedule_date) < getdate(self.transaction_date)):
if (
d.schedule_date
and self.transaction_date
and getdate(d.schedule_date) < getdate(self.transaction_date)
):
frappe.throw(_("Row #{0}: Reqd by Date cannot be before Transaction Date").format(d.idx))
else:
frappe.throw(_("Please enter Reqd by Date"))
def validate_items(self):
# validate items to see if they have is_purchase_item or is_subcontracted_item enabled
if self.doctype=="Material Request": return
if self.doctype == "Material Request":
return
if hasattr(self, "is_subcontracted") and self.is_subcontracted == 'Yes':
if hasattr(self, "is_subcontracted") and self.is_subcontracted == "Yes":
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
else:
validate_item_type(self, "is_purchase_item", "purchase")
def get_asset_item_details(asset_items):
asset_items_data = {}
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
filters = {'name': ('in', asset_items)}):
for d in frappe.get_all(
"Item",
fields=["name", "auto_create_assets", "asset_naming_series"],
filters={"name": ("in", asset_items)},
):
asset_items_data.setdefault(d.name, d)
return asset_items_data
def validate_item_type(doc, fieldname, message):
# iterate through items and check if they are valid sales or purchase items
items = [d.item_code for d in doc.items if d.item_code]
@@ -712,16 +821,28 @@ def validate_item_type(doc, fieldname, message):
item_list = ", ".join(["%s" % frappe.db.escape(d) for d in items])
invalid_items = [d[0] for d in frappe.db.sql("""
invalid_items = [
d[0]
for d in frappe.db.sql(
"""
select item_code from tabItem where name in ({0}) and {1}=0
""".format(item_list, fieldname), as_list=True)]
""".format(
item_list, fieldname
),
as_list=True,
)
]
if invalid_items:
items = ", ".join([d for d in invalid_items])
if len(invalid_items) > 1:
error_message = _("Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
error_message = _(
"Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master"
).format(items, message)
else:
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
error_message = _(
"Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master"
).format(items, message)
frappe.throw(error_message)

View File

@@ -11,24 +11,30 @@ from frappe.utils import cstr, flt
from six import string_types
class ItemVariantExistsError(frappe.ValidationError): pass
class InvalidItemAttributeValueError(frappe.ValidationError): pass
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
class ItemVariantExistsError(frappe.ValidationError):
pass
class InvalidItemAttributeValueError(frappe.ValidationError):
pass
class ItemTemplateCannotHaveStock(frappe.ValidationError):
pass
@frappe.whitelist()
def get_variant(template, args=None, variant=None, manufacturer=None,
manufacturer_part_no=None):
def get_variant(template, args=None, variant=None, manufacturer=None, manufacturer_part_no=None):
"""Validates Attributes and their Values, then looks for an exactly
matching Item Variant
matching Item Variant
:param item: Template Item
:param args: A dictionary with "Attribute" as key and "Attribute Value" as value
:param item: Template Item
:param args: A dictionary with "Attribute" as key and "Attribute Value" as value
"""
item_template = frappe.get_doc('Item', template)
item_template = frappe.get_doc("Item", template)
if item_template.variant_based_on=='Manufacturer' and manufacturer:
return make_variant_based_on_manufacturer(item_template, manufacturer,
manufacturer_part_no)
if item_template.variant_based_on == "Manufacturer" and manufacturer:
return make_variant_based_on_manufacturer(item_template, manufacturer, manufacturer_part_no)
else:
if isinstance(args, string_types):
args = json.loads(args)
@@ -37,28 +43,30 @@ def get_variant(template, args=None, variant=None, manufacturer=None,
frappe.throw(_("Please specify at least one attribute in the Attributes table"))
return find_variant(template, args, variant)
def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part_no):
'''Make and return a new variant based on manufacturer and
manufacturer part no'''
"""Make and return a new variant based on manufacturer and
manufacturer part no"""
from frappe.model.naming import append_number_if_name_exists
variant = frappe.new_doc('Item')
variant = frappe.new_doc("Item")
copy_attributes_to_variant(template, variant)
variant.manufacturer = manufacturer
variant.manufacturer_part_no = manufacturer_part_no
variant.item_code = append_number_if_name_exists('Item', template.name)
variant.item_code = append_number_if_name_exists("Item", template.name)
return variant
def validate_item_variant_attributes(item, args=None):
if isinstance(item, string_types):
item = frappe.get_doc('Item', item)
item = frappe.get_doc("Item", item)
if not args:
args = {d.attribute.lower():d.attribute_value for d in item.attributes}
args = {d.attribute.lower(): d.attribute_value for d in item.attributes}
attribute_values, numeric_values = get_attribute_values(item)
@@ -74,6 +82,7 @@ def validate_item_variant_attributes(item, args=None):
attributes_list = attribute_values.get(attribute.lower(), [])
validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
def validate_is_incremental(numeric_attribute, attribute, value, item):
from_range = numeric_attribute.from_range
to_range = numeric_attribute.to_range
@@ -85,30 +94,48 @@ def validate_is_incremental(numeric_attribute, attribute, value, item):
is_in_range = from_range <= flt(value) <= to_range
precision = max(len(cstr(v).split(".")[-1].rstrip("0")) for v in (value, increment))
#avoid precision error by rounding the remainder
# avoid precision error by rounding the remainder
remainder = flt((flt(value) - from_range) % increment, precision)
is_incremental = remainder==0 or remainder==increment
is_incremental = remainder == 0 or remainder == increment
if not (is_in_range and is_incremental):
frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3} for Item {4}")\
.format(attribute, from_range, to_range, increment, item),
InvalidItemAttributeValueError, title=_('Invalid Attribute'))
frappe.throw(
_(
"Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3} for Item {4}"
).format(attribute, from_range, to_range, increment, item),
InvalidItemAttributeValueError,
title=_("Invalid Attribute"),
)
def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
def validate_item_attribute_value(
attributes_list, attribute, attribute_value, item, from_variant=True
):
allow_rename_attribute_value = frappe.db.get_single_value(
"Item Variant Settings", "allow_rename_attribute_value"
)
if allow_rename_attribute_value:
pass
elif attribute_value not in attributes_list:
if from_variant:
frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
frappe.throw(
_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)
),
InvalidItemAttributeValueError,
title=_("Invalid Value"),
)
else:
msg = _("The value {0} is already assigned to an existing Item {1}.").format(
frappe.bold(attribute_value), frappe.bold(item))
msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
frappe.bold(attribute_value), frappe.bold(item)
)
msg += "<br>" + _(
"To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings."
).format(frappe.bold("Allow Rename Attribute Value"))
frappe.throw(msg, InvalidItemAttributeValueError, title=_("Edit Not Allowed"))
frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
def get_attribute_values(item):
if not frappe.flags.attribute_values:
@@ -117,9 +144,11 @@ def get_attribute_values(item):
for t in frappe.get_all("Item Attribute Value", fields=["parent", "attribute_value"]):
attribute_values.setdefault(t.parent.lower(), []).append(t.attribute_value)
for t in frappe.get_all('Item Variant Attribute',
for t in frappe.get_all(
"Item Variant Attribute",
fields=["attribute", "from_range", "to_range", "increment"],
filters={'numeric_values': 1, 'parent': item.variant_of}):
filters={"numeric_values": 1, "parent": item.variant_of},
):
numeric_values[t.attribute.lower()] = t
frappe.flags.attribute_values = attribute_values
@@ -127,14 +156,22 @@ def get_attribute_values(item):
return frappe.flags.attribute_values, frappe.flags.numeric_values
def find_variant(template, args, variant_item_code=None):
conditions = ["""(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})"""\
.format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
conditions = [
"""(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})""".format(
frappe.db.escape(key), frappe.db.escape(cstr(value))
)
for key, value in args.items()
]
conditions = " or ".join(conditions)
from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes
possible_variants = [i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code]
possible_variants = [
i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code
]
for variant in possible_variants:
variant = frappe.get_doc("Item", variant)
@@ -146,7 +183,7 @@ def find_variant(template, args, variant_item_code=None):
for attribute, value in args.items():
for row in variant.attributes:
if row.attribute==attribute and row.attribute_value== cstr(value):
if row.attribute == attribute and row.attribute_value == cstr(value):
# this row matches
match_count += 1
break
@@ -154,6 +191,7 @@ def find_variant(template, args, variant_item_code=None):
if match_count == len(args.keys()):
return variant.name
@frappe.whitelist()
def create_variant(item, args):
if isinstance(args, string_types):
@@ -161,14 +199,11 @@ def create_variant(item, args):
template = frappe.get_doc("Item", item)
variant = frappe.new_doc("Item")
variant.variant_based_on = 'Item Attribute'
variant.variant_based_on = "Item Attribute"
variant_attributes = []
for d in template.attributes:
variant_attributes.append({
"attribute": d.attribute,
"attribute_value": args.get(d.attribute)
})
variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)})
variant.set("attributes", variant_attributes)
copy_attributes_to_variant(template, variant)
@@ -176,6 +211,7 @@ def create_variant(item, args):
return variant
@frappe.whitelist()
def enqueue_multiple_variant_creation(item, args):
# There can be innumerable attribute combinations, enqueue
@@ -190,9 +226,14 @@ def enqueue_multiple_variant_creation(item, args):
if total_variants < 10:
return create_multiple_variants(item, args)
else:
frappe.enqueue("erpnext.controllers.item_variant.create_multiple_variants",
item=item, args=args, now=frappe.flags.in_test);
return 'queued'
frappe.enqueue(
"erpnext.controllers.item_variant.create_multiple_variants",
item=item,
args=args,
now=frappe.flags.in_test,
)
return "queued"
def create_multiple_variants(item, args):
count = 0
@@ -205,26 +246,27 @@ def create_multiple_variants(item, args):
if not get_variant(item, args=attribute_values):
variant = create_variant(item, attribute_values)
variant.save()
count +=1
count += 1
return count
def generate_keyed_value_combinations(args):
"""
From this:
args = {"attr1": ["a", "b", "c"], "attr2": ["1", "2"], "attr3": ["A"]}
args = {"attr1": ["a", "b", "c"], "attr2": ["1", "2"], "attr3": ["A"]}
To this:
[
{u'attr1': u'a', u'attr2': u'1', u'attr3': u'A'},
{u'attr1': u'b', u'attr2': u'1', u'attr3': u'A'},
{u'attr1': u'c', u'attr2': u'1', u'attr3': u'A'},
{u'attr1': u'a', u'attr2': u'2', u'attr3': u'A'},
{u'attr1': u'b', u'attr2': u'2', u'attr3': u'A'},
{u'attr1': u'c', u'attr2': u'2', u'attr3': u'A'}
]
[
{u'attr1': u'a', u'attr2': u'1', u'attr3': u'A'},
{u'attr1': u'b', u'attr2': u'1', u'attr3': u'A'},
{u'attr1': u'c', u'attr2': u'1', u'attr3': u'A'},
{u'attr1': u'a', u'attr2': u'2', u'attr3': u'A'},
{u'attr1': u'b', u'attr2': u'2', u'attr3': u'A'},
{u'attr1': u'c', u'attr2': u'2', u'attr3': u'A'}
]
"""
# Return empty list if empty
@@ -260,17 +302,27 @@ def generate_keyed_value_combinations(args):
return results
def copy_attributes_to_variant(item, variant):
# copy non no-copy fields
exclude_fields = ["naming_series", "item_code", "item_name", "published_in_website",
"opening_stock", "variant_of", "valuation_rate", "has_variants", "attributes"]
exclude_fields = [
"naming_series",
"item_code",
"item_name",
"published_in_website",
"opening_stock",
"variant_of",
"valuation_rate",
"has_variants",
"attributes",
]
if item.variant_based_on=='Manufacturer':
if item.variant_based_on == "Manufacturer":
# don't copy manufacturer values if based on part no
exclude_fields += ['manufacturer', 'manufacturer_part_no']
exclude_fields += ["manufacturer", "manufacturer_part_no"]
allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields = ['field_name'])]
allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields=["field_name"])]
if "variant_based_on" not in allow_fields:
allow_fields.append("variant_based_on")
for field in item.meta.fields:
@@ -289,11 +341,11 @@ def copy_attributes_to_variant(item, variant):
variant.variant_of = item.name
if 'description' not in allow_fields:
if "description" not in allow_fields:
if not variant.description:
variant.description = ""
variant.description = ""
else:
if item.variant_based_on=='Item Attribute':
if item.variant_based_on == "Item Attribute":
if variant.attributes:
attributes_description = item.description + " "
for d in variant.attributes:
@@ -302,6 +354,7 @@ def copy_attributes_to_variant(item, variant):
if attributes_description not in variant.description:
variant.description = attributes_description
def make_variant_item_code(template_item_code, template_item_name, variant):
"""Uses template's item code and abbreviations to make variant's item code"""
if variant.item_code:
@@ -309,13 +362,14 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
abbreviations = []
for attr in variant.attributes:
item_attribute = frappe.db.sql("""select i.numeric_values, v.abbr
item_attribute = frappe.db.sql(
"""select i.numeric_values, v.abbr
from `tabItem Attribute` i left join `tabItem Attribute Value` v
on (i.name=v.parent)
where i.name=%(attribute)s and (v.attribute_value=%(attribute_value)s or i.numeric_values = 1)""", {
"attribute": attr.attribute,
"attribute_value": attr.attribute_value
}, as_dict=True)
where i.name=%(attribute)s and (v.attribute_value=%(attribute_value)s or i.numeric_values = 1)""",
{"attribute": attr.attribute, "attribute_value": attr.attribute_value},
as_dict=True,
)
if not item_attribute:
continue
@@ -323,13 +377,16 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
# frappe.bold(attr.attribute_value)), title=_('Invalid Attribute'),
# exc=InvalidItemAttributeValueError)
abbr_or_value = cstr(attr.attribute_value) if item_attribute[0].numeric_values else item_attribute[0].abbr
abbr_or_value = (
cstr(attr.attribute_value) if item_attribute[0].numeric_values else item_attribute[0].abbr
)
abbreviations.append(abbr_or_value)
if abbreviations:
variant.item_code = "{0}-{1}".format(template_item_code, "-".join(abbreviations))
variant.item_name = "{0}-{1}".format(template_item_name, "-".join(abbreviations))
@frappe.whitelist()
def create_variant_doc_for_quick_entry(template, args):
variant_based_on = frappe.db.get_value("Item", template, "variant_based_on")

View File

@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
def set_print_templates_for_item_table(doc, settings):
doc.print_templates = {
"items": "templates/print_formats/includes/items.html",
@@ -20,16 +19,21 @@ def set_print_templates_for_item_table(doc, settings):
doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"]
if settings.compact_item_print:
doc.child_print_templates["items"]["description"] =\
"templates/print_formats/includes/item_table_description.html"
doc.child_print_templates["items"][
"description"
] = "templates/print_formats/includes/item_table_description.html"
doc.flags.format_columns = format_columns
def set_print_templates_for_taxes(doc, settings):
doc.flags.show_inclusive_tax_in_print = doc.is_inclusive_tax()
doc.print_templates.update({
"total": "templates/print_formats/includes/total.html",
"taxes": "templates/print_formats/includes/taxes.html"
})
doc.print_templates.update(
{
"total": "templates/print_formats/includes/total.html",
"taxes": "templates/print_formats/includes/taxes.html",
}
)
def format_columns(display_columns, compact_fields):
compact_fields = compact_fields + ["image", "item_code", "item_name"]

View File

@@ -21,7 +21,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
conditions = []
fields = get_fields("Employee", ["name", "employee_name"])
return frappe.db.sql("""select {fields} from `tabEmployee`
return frappe.db.sql(
"""select {fields} from `tabEmployee`
where status in ('Active', 'Suspended')
and docstatus < 2
and ({key} like %(txt)s
@@ -32,17 +33,16 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
idx desc,
name, employee_name
limit %(start)s, %(page_len)s""".format(**{
'fields': ", ".join(fields),
'key': searchfield,
'fcond': get_filters_cond(doctype, filters, conditions),
'mcond': get_match_cond(doctype)
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})
limit %(start)s, %(page_len)s""".format(
**{
"fields": ", ".join(fields),
"key": searchfield,
"fcond": get_filters_cond(doctype, filters, conditions),
"mcond": get_match_cond(doctype),
}
),
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
)
# searches for leads which are not converted
@@ -51,7 +51,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
def lead_query(doctype, txt, searchfield, start, page_len, filters):
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
return frappe.db.sql("""select {fields} from `tabLead`
return frappe.db.sql(
"""select {fields} from `tabLead`
where docstatus < 2
and ifnull(status, '') != 'Converted'
and ({key} like %(txt)s
@@ -64,19 +65,15 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, company_name), locate(%(_txt)s, company_name), 99999),
idx desc,
name, lead_name
limit %(start)s, %(page_len)s""".format(**{
'fields': ", ".join(fields),
'key': searchfield,
'mcond':get_match_cond(doctype)
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})
limit %(start)s, %(page_len)s""".format(
**{"fields": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
),
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
)
# searches for customer
# searches for customer
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def customer_query(doctype, txt, searchfield, start, page_len, filters):
@@ -93,7 +90,8 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
searchfields = frappe.get_meta("Customer").get_search_fields()
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
return frappe.db.sql("""select {fields} from `tabCustomer`
return frappe.db.sql(
"""select {fields} from `tabCustomer`
where docstatus < 2
and ({scond}) and disabled=0
{fcond} {mcond}
@@ -102,17 +100,16 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, customer_name), locate(%(_txt)s, customer_name), 99999),
idx desc,
name, customer_name
limit %(start)s, %(page_len)s""".format(**{
"fields": ", ".join(fields),
"scond": searchfields,
"mcond": get_match_cond(doctype),
"fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})
limit %(start)s, %(page_len)s""".format(
**{
"fields": ", ".join(fields),
"scond": searchfields,
"mcond": get_match_cond(doctype),
"fcond": get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
}
),
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
)
# searches for supplier
@@ -128,7 +125,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
fields = get_fields("Supplier", fields)
return frappe.db.sql("""select {field} from `tabSupplier`
return frappe.db.sql(
"""select {field} from `tabSupplier`
where docstatus < 2
and ({key} like %(txt)s
or supplier_name like %(txt)s) and disabled=0
@@ -139,29 +137,25 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, supplier_name), locate(%(_txt)s, supplier_name), 99999),
idx desc,
name, supplier_name
limit %(start)s, %(page_len)s """.format(**{
'field': ', '.join(fields),
'key': searchfield,
'mcond':get_match_cond(doctype)
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})
limit %(start)s, %(page_len)s """.format(
**{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
),
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
company_currency = erpnext.get_company_currency(filters.get('company'))
company_currency = erpnext.get_company_currency(filters.get("company"))
def get_accounts(with_account_type_filter):
account_type_condition = ''
account_type_condition = ""
if with_account_type_filter:
account_type_condition = "AND account_type in %(account_types)s"
accounts = frappe.db.sql("""
accounts = frappe.db.sql(
"""
SELECT name, parent_account
FROM `tabAccount`
WHERE `tabAccount`.docstatus!=2
@@ -176,7 +170,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
""".format(
account_type_condition=account_type_condition,
searchfield=searchfield,
mcond=get_match_cond(doctype)
mcond=get_match_cond(doctype),
),
dict(
account_types=filters.get("account_type"),
@@ -184,8 +178,8 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
currency=company_currency,
txt="%{}%".format(txt),
offset=start,
limit=page_len
)
limit=page_len,
),
)
return accounts
@@ -206,7 +200,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if isinstance(filters, str):
filters = json.loads(filters)
#Get searchfields from meta and use in Item Link field query
# Get searchfields from meta and use in Item Link field query
meta = frappe.get_meta("Item", cached=True)
searchfields = meta.get_search_fields()
@@ -216,49 +210,56 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if ignored_field in searchfields:
searchfields.remove(ignored_field)
columns = ''
extra_searchfields = [field for field in searchfields
if not field in ["name", "item_group", "description", "item_name"]]
columns = ""
extra_searchfields = [
field
for field in searchfields
if not field in ["name", "item_group", "description", "item_name"]
]
if extra_searchfields:
columns = ", " + ", ".join(extra_searchfields)
searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"]
if not field in searchfields]
searchfields = searchfields + [
field
for field in [searchfield or "name", "item_code", "item_group", "item_name"]
if not field in searchfields
]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
if filters and isinstance(filters, dict):
if filters.get('customer') or filters.get('supplier'):
party = filters.get('customer') or filters.get('supplier')
item_rules_list = frappe.get_all('Party Specific Item',
filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
if filters.get("customer") or filters.get("supplier"):
party = filters.get("customer") or filters.get("supplier")
item_rules_list = frappe.get_all(
"Party Specific Item", filters={"party": party}, fields=["restrict_based_on", "based_on_value"]
)
filters_dict = {}
for rule in item_rules_list:
if rule['restrict_based_on'] == 'Item':
rule['restrict_based_on'] = 'name'
if rule["restrict_based_on"] == "Item":
rule["restrict_based_on"] = "name"
filters_dict[rule.restrict_based_on] = []
for rule in item_rules_list:
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
for filter in filters_dict:
filters[scrub(filter)] = ['in', filters_dict[filter]]
filters[scrub(filter)] = ["in", filters_dict[filter]]
if filters.get('customer'):
del filters['customer']
if filters.get("customer"):
del filters["customer"]
else:
del filters['supplier']
del filters["supplier"]
else:
filters.pop('customer', None)
filters.pop('supplier', None)
filters.pop("customer", None)
filters.pop("supplier", None)
description_cond = ''
if frappe.db.count('Item', cache=True) < 50000:
description_cond = ""
if frappe.db.count("Item", cache=True) < 50000:
# scan description only if items are less than 50000
description_cond = 'or tabItem.description LIKE %(txt)s'
return frappe.db.sql("""select
description_cond = "or tabItem.description LIKE %(txt)s"
return frappe.db.sql(
"""select
tabItem.name, tabItem.item_name, tabItem.item_group,
if(length(tabItem.description) > 40, \
concat(substr(tabItem.description, 1, 40), "..."), description) as description
@@ -279,16 +280,19 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
limit %(start)s, %(page_len)s """.format(
columns=columns,
scond=searchfields,
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%'),
description_cond = description_cond),
{
"today": nowdate(),
"txt": "%%%s%%" % txt,
"_txt": txt.replace("%", ""),
"start": start,
"page_len": page_len
}, as_dict=as_dict)
fcond=get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
mcond=get_match_cond(doctype).replace("%", "%%"),
description_cond=description_cond,
),
{
"today": nowdate(),
"txt": "%%%s%%" % txt,
"_txt": txt.replace("%", ""),
"start": start,
"page_len": page_len,
},
as_dict=as_dict,
)
@frappe.whitelist()
@@ -297,7 +301,8 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
conditions = []
fields = get_fields("BOM", ["name", "item"])
return frappe.db.sql("""select {fields}
return frappe.db.sql(
"""select {fields}
from tabBOM
where tabBOM.docstatus=1
and tabBOM.is_active=1
@@ -308,30 +313,35 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
idx desc, name
limit %(start)s, %(page_len)s """.format(
fields=", ".join(fields),
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%'),
key=searchfield),
fcond=get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
mcond=get_match_cond(doctype).replace("%", "%%"),
key=searchfield,
),
{
'txt': '%' + txt + '%',
'_txt': txt.replace("%", ""),
'start': start or 0,
'page_len': page_len or 20
})
"txt": "%" + txt + "%",
"_txt": txt.replace("%", ""),
"start": start or 0,
"page_len": page_len or 20,
},
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
cond = ''
if filters and filters.get('customer'):
cond = ""
if filters and filters.get("customer"):
cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
ifnull(`tabProject`.customer,"")="") and""" % (
frappe.db.escape(filters.get("customer"))
)
fields = get_fields("Project", ["name", "project_name"])
searchfields = frappe.get_meta("Project").get_search_fields()
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
return frappe.db.sql("""select {fields} from `tabProject`
return frappe.db.sql(
"""select {fields} from `tabProject`
where
`tabProject`.status not in ("Completed", "Cancelled")
and {cond} {scond} {match_cond}
@@ -340,15 +350,15 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
idx desc,
`tabProject`.name asc
limit {start}, {page_len}""".format(
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
fields=", ".join(["`tabProject`.{0}".format(f) for f in fields]),
cond=cond,
scond=searchfields,
match_cond=get_match_cond(doctype),
start=start,
page_len=page_len), {
"txt": "%{0}%".format(txt),
"_txt": txt.replace('%', '')
})
page_len=page_len,
),
{"txt": "%{0}%".format(txt), "_txt": txt.replace("%", "")},
)
@frappe.whitelist()
@@ -356,7 +366,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
return frappe.db.sql("""
return frappe.db.sql(
"""
select %(fields)s
from `tabDelivery Note`
where `tabDelivery Note`.`%(key)s` like %(txt)s and
@@ -371,15 +382,19 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
)
)
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
""" % {
"fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
"key": searchfield,
"fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype),
"start": start,
"page_len": page_len,
"txt": "%(txt)s"
}, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict)
"""
% {
"fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
"key": searchfield,
"fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype),
"start": start,
"page_len": page_len,
"txt": "%(txt)s",
},
{"txt": ("%%%s%%" % txt)},
as_dict=as_dict,
)
@frappe.whitelist()
@@ -391,12 +406,12 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
batch_nos = None
args = {
'item_code': filters.get("item_code"),
'warehouse': filters.get("warehouse"),
'posting_date': filters.get('posting_date'),
'txt': "%{0}%".format(txt),
"item_code": filters.get("item_code"),
"warehouse": filters.get("warehouse"),
"posting_date": filters.get("posting_date"),
"txt": "%{0}%".format(txt),
"start": start,
"page_len": page_len
"page_len": page_len,
}
having_clause = "having sum(sle.actual_qty) > 0"
@@ -406,20 +421,21 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
meta = frappe.get_meta("Batch", cached=True)
searchfields = meta.get_search_fields()
search_columns = ''
search_cond = ''
search_columns = ""
search_cond = ""
if searchfields:
search_columns = ", " + ", ".join(searchfields)
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
if args.get('warehouse'):
searchfields = ['batch.' + field for field in searchfields]
if args.get("warehouse"):
searchfields = ["batch." + field for field in searchfields]
if searchfields:
search_columns = ", " + ", ".join(searchfields)
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
batch_nos = frappe.db.sql(
"""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
{search_columns}
from `tabStock Ledger Entry` sle
@@ -439,16 +455,19 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
group by batch_no {having_clause}
order by batch.expiry_date, sle.batch_no desc
limit %(start)s, %(page_len)s""".format(
search_columns = search_columns,
search_columns=search_columns,
cond=cond,
match_conditions=get_match_cond(doctype),
having_clause = having_clause,
search_cond = search_cond
), args)
having_clause=having_clause,
search_cond=search_cond,
),
args,
)
return batch_nos
else:
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
return frappe.db.sql(
"""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
{search_columns}
from `tabBatch` batch
where batch.disabled = 0
@@ -462,8 +481,14 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
{match_conditions}
order by expiry_date, name desc
limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns,
search_cond = search_cond, match_conditions=get_match_cond(doctype)), args)
limit %(start)s, %(page_len)s""".format(
cond,
search_columns=search_columns,
search_cond=search_cond,
match_conditions=get_match_cond(doctype),
),
args,
)
@frappe.whitelist()
@@ -486,25 +511,33 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
if searchfield and txt:
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
return frappe.desk.reportview.execute("Account", filters = filter_list,
fields = ["name", "parent_account"],
limit_start=start, limit_page_length=page_len, as_list=True)
return frappe.desk.reportview.execute(
"Account",
filters=filter_list,
fields=["name", "parent_account"],
limit_start=start,
limit_page_length=page_len,
as_list=True,
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
return frappe.db.sql(
"""select distinct bo.name, bo.blanket_order_type, bo.to_date
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
where
boi.parent = bo.name
and boi.item_code = {item_code}
and bo.blanket_order_type = '{blanket_order_type}'
and bo.company = {company}
and bo.docstatus = 1"""
.format(item_code = frappe.db.escape(filters.get("item")),
blanket_order_type = filters.get("blanket_order_type"),
company = frappe.db.escape(filters.get("company"))
))
and bo.docstatus = 1""".format(
item_code=frappe.db.escape(filters.get("item")),
blanket_order_type=filters.get("blanket_order_type"),
company=frappe.db.escape(filters.get("company")),
)
)
@frappe.whitelist()
@@ -515,23 +548,26 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
# income account can be any Credit account,
# but can also be a Asset account with account_type='Income Account' in special circumstances.
# Hence the first condition is an "OR"
if not filters: filters = {}
if not filters:
filters = {}
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
return frappe.db.sql("""select tabAccount.name from `tabAccount`
return frappe.db.sql(
"""select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Income Account", "Temporary"))
and tabAccount.is_group=0
and tabAccount.`{key}` LIKE %(txt)s
{condition} {match_condition}
order by idx desc, name"""
.format(condition=condition, match_condition=get_match_cond(doctype), key=searchfield), {
'txt': '%' + txt + '%',
'company': filters.get("company", "")
})
order by idx desc, name""".format(
condition=condition, match_condition=get_match_cond(doctype), key=searchfield
),
{"txt": "%" + txt + "%", "company": filters.get("company", "")},
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -539,68 +575,73 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
dimension_filters = get_dimension_filter_map()
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
dimension_filters = dimension_filters.get((filters.get("dimension"), filters.get("account")))
query_filters = []
or_filters = []
fields = ['name']
fields = ["name"]
searchfields = frappe.get_meta(doctype).get_search_fields()
meta = frappe.get_meta(doctype)
if meta.is_tree:
query_filters.append(['is_group', '=', 0])
query_filters.append(["is_group", "=", 0])
if meta.has_field('disabled'):
query_filters.append(['disabled', '!=', 1])
if meta.has_field("disabled"):
query_filters.append(["disabled", "!=", 1])
if meta.has_field('company'):
query_filters.append(['company', '=', filters.get('company')])
if meta.has_field("company"):
query_filters.append(["company", "=", filters.get("company")])
for field in searchfields:
or_filters.append([field, 'LIKE', "%%%s%%" % txt])
or_filters.append([field, "LIKE", "%%%s%%" % txt])
fields.append(field)
if dimension_filters:
if dimension_filters['allow_or_restrict'] == 'Allow':
query_selector = 'in'
if dimension_filters["allow_or_restrict"] == "Allow":
query_selector = "in"
else:
query_selector = 'not in'
query_selector = "not in"
if len(dimension_filters['allowed_dimensions']) == 1:
dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
if len(dimension_filters["allowed_dimensions"]) == 1:
dimensions = tuple(dimension_filters["allowed_dimensions"] * 2)
else:
dimensions = tuple(dimension_filters['allowed_dimensions'])
dimensions = tuple(dimension_filters["allowed_dimensions"])
query_filters.append(['name', query_selector, dimensions])
query_filters.append(["name", query_selector, dimensions])
output = frappe.get_list(doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1)
output = frappe.get_list(
doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1
)
return [tuple(d) for d in set(output)]
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
if not filters: filters = {}
if not filters:
filters = {}
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
return frappe.db.sql("""select tabAccount.name from `tabAccount`
return frappe.db.sql(
"""select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
and tabAccount.is_group=0
and tabAccount.docstatus!=2
and tabAccount.{key} LIKE %(txt)s
{condition} {match_condition}"""
.format(condition=condition, key=searchfield,
match_condition=get_match_cond(doctype)), {
'company': filters.get("company", ""),
'txt': '%' + txt + '%'
})
{condition} {match_condition}""".format(
condition=condition, key=searchfield, match_condition=get_match_cond(doctype)
),
{"company": filters.get("company", ""), "txt": "%" + txt + "%"},
)
@frappe.whitelist()
@@ -621,14 +662,16 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
limit
{start}, {page_len}
""".format(
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True),
key=searchfield,
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
mcond=get_match_cond(doctype),
start=start,
page_len=page_len,
txt=frappe.db.escape('%{0}%'.format(txt))
)
bin_conditions=get_filters_cond(
doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
),
key=searchfield,
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
mcond=get_match_cond(doctype),
start=start,
page_len=page_len,
txt=frappe.db.escape("%{0}%".format(txt)),
)
return frappe.db.sql(query)
@@ -647,10 +690,12 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
query = """select batch_id from `tabBatch`
where disabled = 0
and (expiry_date >= CURDATE() or expiry_date IS NULL)
and name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
and name like {txt}""".format(
txt=frappe.db.escape("%{0}%".format(txt))
)
if filters and filters.get('item'):
query += " and item = {item}".format(item = frappe.db.escape(filters.get('item')))
if filters and filters.get("item"):
query += " and item = {item}".format(item=frappe.db.escape(filters.get("item")))
return frappe.db.sql(query, filters)
@@ -659,8 +704,8 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
@frappe.validate_and_sanitize_search_inputs
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
item_filters = [
['manufacturer', 'like', '%' + txt + '%'],
['item_code', '=', filters.get("item_code")]
["manufacturer", "like", "%" + txt + "%"],
["item_code", "=", filters.get("item_code")],
]
item_manufacturers = frappe.get_all(
@@ -669,7 +714,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
filters=item_filters,
limit_start=start,
limit_page_length=page_len,
as_list=1
as_list=1,
)
return item_manufacturers
@@ -681,10 +726,14 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
select pr.name
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem
where pr.docstatus = 1 and pritem.parent = pr.name
and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
and pr.name like {txt}""".format(
txt=frappe.db.escape("%{0}%".format(txt))
)
if filters and filters.get('item_code'):
query += " and pritem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
if filters and filters.get("item_code"):
query += " and pritem.item_code = {item_code}".format(
item_code=frappe.db.escape(filters.get("item_code"))
)
return frappe.db.sql(query, filters)
@@ -696,10 +745,14 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
select pi.name
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem
where pi.docstatus = 1 and piitem.parent = pi.name
and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
and pi.name like {txt}""".format(
txt=frappe.db.escape("%{0}%".format(txt))
)
if filters and filters.get('item_code'):
query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
if filters and filters.get("item_code"):
query += " and piitem.item_code = {item_code}".format(
item_code=frappe.db.escape(filters.get("item_code"))
)
return frappe.db.sql(query, filters)
@@ -714,18 +767,22 @@ def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, fil
is_group = 0
and company = {company}
and name like {txt}""".format(
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
company=frappe.db.escape(filters.get("company")), txt=frappe.db.escape("%{0}%".format(txt))
)
if filters and filters.get('inpatient_record'):
if filters and filters.get("inpatient_record"):
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import (
get_current_healthcare_service_unit,
)
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
service_unit = get_current_healthcare_service_unit(filters.get("inpatient_record"))
# if the patient is admitted, then appointments should be allowed against the admission service unit,
# inspite of it being an Inpatient Occupancy service unit
if service_unit:
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
query += " and (allow_appointments = 1 or name = {service_unit})".format(
service_unit=frappe.db.escape(service_unit)
)
else:
query += " and allow_appointments = 1"
else:
@@ -738,27 +795,29 @@ def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, fil
@frappe.validate_and_sanitize_search_inputs
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
item_group = filters.get('item_group')
company = filters.get('company')
item_doc = frappe.get_cached_doc("Item", filters.get("item_code"))
item_group = filters.get("item_group")
company = filters.get("company")
taxes = item_doc.taxes or []
while item_group:
item_group_doc = frappe.get_cached_doc('Item Group', item_group)
item_group_doc = frappe.get_cached_doc("Item Group", item_group)
taxes += item_group_doc.taxes or []
item_group = item_group_doc.parent_item_group
if not taxes:
return frappe.get_all('Item Tax Template', filters={'disabled': 0, 'company': company}, as_list=True)
return frappe.get_all(
"Item Tax Template", filters={"disabled": 0, "company": company}, as_list=True
)
else:
valid_from = filters.get('valid_from')
valid_from = filters.get("valid_from")
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
args = {
'item_code': filters.get('item_code'),
'posting_date': valid_from,
'tax_category': filters.get('tax_category'),
'company': company
"item_code": filters.get("item_code"),
"posting_date": valid_from,
"tax_category": filters.get("tax_category"),
"company": company,
}
taxes = _get_item_tax_template(args, taxes, for_validate=True)

View File

@@ -11,7 +11,9 @@ import erpnext
from erpnext.stock.utils import get_incoming_rate
class StockOverReturnError(frappe.ValidationError): pass
class StockOverReturnError(frappe.ValidationError):
pass
def validate_return(doc):
if not doc.meta.get_field("is_return") or not doc.is_return:
@@ -21,32 +23,50 @@ def validate_return(doc):
validate_return_against(doc)
validate_returned_items(doc)
def validate_return_against(doc):
if not frappe.db.exists(doc.doctype, doc.return_against):
frappe.throw(_("Invalid {0}: {1}")
.format(doc.meta.get_label("return_against"), doc.return_against))
frappe.throw(
_("Invalid {0}: {1}").format(doc.meta.get_label("return_against"), doc.return_against)
)
else:
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
if (
ref_doc.company == doc.company
and ref_doc.get(party_type) == doc.get(party_type)
and ref_doc.docstatus == 1
):
# validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
ref_posting_datetime = "%s %s" % (
ref_doc.posting_date,
ref_doc.get("posting_time") or "00:00:00",
)
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
frappe.throw(
_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime))
)
# validate same exchange rate
if doc.conversion_rate != ref_doc.conversion_rate:
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
frappe.throw(
_("Exchange Rate must be same as {0} {1} ({2})").format(
doc.doctype, doc.return_against, ref_doc.conversion_rate
)
)
# validate update stock
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
.format(doc.return_against))
frappe.throw(
_("'Update Stock' can not be checked because items are not delivered via {0}").format(
doc.return_against
)
)
def validate_returned_items(doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -54,43 +74,61 @@ def validate_returned_items(doc):
valid_items = frappe._dict()
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
if doc.doctype != 'Purchase Invoice':
if doc.doctype != "Purchase Invoice":
select_fields += ",serial_no, batch_no"
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
select_fields += ",rejected_qty, received_qty"
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s"""
.format(select_fields, doc.doctype), doc.return_against, as_dict=1):
valid_items = get_ref_item_dict(valid_items, d)
for d in frappe.db.sql(
"""select {0} from `tab{1} Item` where parent = %s""".format(select_fields, doc.doctype),
doc.return_against,
as_dict=1,
):
valid_items = get_ref_item_dict(valid_items, d)
if doc.doctype in ("Delivery Note", "Sales Invoice"):
for d in frappe.db.sql("""select item_code, qty, serial_no, batch_no from `tabPacked Item`
where parent = %s""", doc.return_against, as_dict=1):
valid_items = get_ref_item_dict(valid_items, d)
for d in frappe.db.sql(
"""select item_code, qty, serial_no, batch_no from `tabPacked Item`
where parent = %s""",
doc.return_against,
as_dict=1,
):
valid_items = get_ref_item_dict(valid_items, d)
already_returned_items = get_already_returned_items(doc)
# ( not mandatory when it is Purchase Invoice or a Sales Invoice without Update Stock )
warehouse_mandatory = not ((doc.doctype=="Purchase Invoice" or doc.doctype=="Sales Invoice") and not doc.update_stock)
warehouse_mandatory = not (
(doc.doctype == "Purchase Invoice" or doc.doctype == "Sales Invoice") and not doc.update_stock
)
items_returned = False
for d in doc.get("items"):
if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0):
if d.item_code not in valid_items:
frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}")
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
frappe.throw(
_("Row # {0}: Returned Item {1} does not exist in {2} {3}").format(
d.idx, d.item_code, doc.doctype, doc.return_against
)
)
else:
ref = valid_items.get(d.item_code, frappe._dict())
validate_quantity(doc, d, ref, valid_items, already_returned_items)
if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt(d.rate) > ref.rate:
frappe.throw(_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}")
.format(d.idx, doc.doctype, doc.return_against))
frappe.throw(
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
d.idx, doc.doctype, doc.return_against
)
)
elif ref.batch_no and d.batch_no not in ref.batch_no:
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
.format(d.idx, doc.doctype, doc.return_against))
frappe.throw(
_("Row # {0}: Batch No must be same as {1} {2}").format(
d.idx, doc.doctype, doc.return_against
)
)
elif ref.serial_no:
if not d.serial_no:
@@ -99,11 +137,16 @@ def validate_returned_items(doc):
serial_nos = get_serial_nos(d.serial_no)
for s in serial_nos:
if s not in ref.serial_no:
frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}")
.format(d.idx, s, doc.doctype, doc.return_against))
frappe.throw(
_("Row # {0}: Serial No {1} does not match with {2} {3}").format(
d.idx, s, doc.doctype, doc.return_against
)
)
if (warehouse_mandatory and not d.get("warehouse") and
frappe.db.get_value("Item", d.item_code, "is_stock_item")
if (
warehouse_mandatory
and not d.get("warehouse")
and frappe.db.get_value("Item", d.item_code, "is_stock_item")
):
frappe.throw(_("Warehouse is mandatory"))
@@ -115,21 +158,23 @@ def validate_returned_items(doc):
if not items_returned:
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
fields = ['stock_qty']
if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']:
fields.extend(['received_qty', 'rejected_qty'])
fields = ["stock_qty"]
if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]:
fields.extend(["received_qty", "rejected_qty"])
already_returned_data = already_returned_items.get(args.item_code) or {}
company_currency = erpnext.get_company_currency(doc.company)
stock_qty_precision = get_field_precision(frappe.get_meta(doc.doctype + " Item")
.get_field("stock_qty"), company_currency)
stock_qty_precision = get_field_precision(
frappe.get_meta(doc.doctype + " Item").get_field("stock_qty"), company_currency
)
for column in fields:
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
if column == 'stock_qty':
if column == "stock_qty":
reference_qty = ref.get(column)
current_stock_qty = args.get(column)
else:
@@ -137,38 +182,49 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
max_returnable_qty = flt(reference_qty, stock_qty_precision) - returned_qty
label = column.replace('_', ' ').title()
label = column.replace("_", " ").title()
if reference_qty:
if flt(args.get(column)) > 0:
frappe.throw(_("{0} must be negative in return document").format(label))
elif returned_qty >= reference_qty and args.get(column):
frappe.throw(_("Item {0} has already been returned")
.format(args.item_code), StockOverReturnError)
frappe.throw(
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
)
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
.format(args.idx, max_returnable_qty, args.item_code), StockOverReturnError)
frappe.throw(
_("Row # {0}: Cannot return more than {1} for Item {2}").format(
args.idx, max_returnable_qty, args.item_code
),
StockOverReturnError,
)
def get_ref_item_dict(valid_items, ref_item_row):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
valid_items.setdefault(ref_item_row.item_code, frappe._dict({
"qty": 0,
"rate": 0,
"stock_qty": 0,
"rejected_qty": 0,
"received_qty": 0,
"serial_no": [],
"conversion_factor": ref_item_row.get("conversion_factor", 1),
"batch_no": []
}))
valid_items.setdefault(
ref_item_row.item_code,
frappe._dict(
{
"qty": 0,
"rate": 0,
"stock_qty": 0,
"rejected_qty": 0,
"received_qty": 0,
"serial_no": [],
"conversion_factor": ref_item_row.get("conversion_factor", 1),
"batch_no": [],
}
),
)
item_dict = valid_items[ref_item_row.item_code]
item_dict["qty"] += ref_item_row.qty
item_dict["stock_qty"] += ref_item_row.get('stock_qty', 0)
item_dict["stock_qty"] += ref_item_row.get("stock_qty", 0)
if ref_item_row.get("rate", 0) > item_dict["rate"]:
item_dict["rate"] = ref_item_row.get("rate", 0)
if ref_item_row.parenttype in ['Purchase Invoice', 'Purchase Receipt']:
if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt"]:
item_dict["received_qty"] += ref_item_row.received_qty
item_dict["rejected_qty"] += ref_item_row.rejected_qty
@@ -180,13 +236,15 @@ def get_ref_item_dict(valid_items, ref_item_row):
return valid_items
def get_already_returned_items(doc):
column = 'child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty'
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
data = frappe.db.sql("""
data = frappe.db.sql(
"""
select {0}
from
`tab{1} Item` child, `tab{2}` par
@@ -194,62 +252,79 @@ def get_already_returned_items(doc):
child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s
group by item_code
""".format(column, doc.doctype, doc.doctype), doc.return_against, as_dict=1)
""".format(
column, doc.doctype, doc.doctype
),
doc.return_against,
as_dict=1,
)
items = {}
for d in data:
items.setdefault(d.item_code, frappe._dict({
"qty": d.get("qty"),
"stock_qty": d.get("stock_qty"),
"received_qty": d.get("received_qty"),
"rejected_qty": d.get("rejected_qty")
}))
items.setdefault(
d.item_code,
frappe._dict(
{
"qty": d.get("qty"),
"stock_qty": d.get("stock_qty"),
"received_qty": d.get("received_qty"),
"rejected_qty": d.get("rejected_qty"),
}
),
)
return items
def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
child_doctype = doctype + " Item"
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
if doctype in ('Purchase Receipt', 'Purchase Invoice'):
party_type = 'supplier'
if doctype in ("Purchase Receipt", "Purchase Invoice"):
party_type = "supplier"
else:
party_type = 'customer'
party_type = "customer"
fields = [
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype),
]
if doctype in ("Purchase Receipt", "Purchase Invoice"):
fields += [
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype)
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
]
if doctype == "Purchase Receipt":
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
# Used retrun against and supplier and is_retrun because there is an index added for it
data = frappe.db.get_list(doctype,
fields = fields,
filters = [
data = frappe.db.get_list(
doctype,
fields=fields,
filters=[
[doctype, "return_against", "=", return_against],
[doctype, party_type, "=", party],
[doctype, "docstatus", "=", 1],
[doctype, "is_return", "=", 1],
[child_doctype, reference_field, "=", row_name]
])
[child_doctype, reference_field, "=", row_name],
],
)
return data[0]
def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
default_warehouse_for_sales_return = frappe.db.get_value(
"Company", company, "default_warehouse_for_sales_return"
)
def set_missing_values(source, target):
doc = frappe.get_doc(target)
@@ -273,29 +348,34 @@ def make_return_doc(doctype, source_name, target_doc=None):
tax.tax_amount = -1 * tax.tax_amount
if doc.get("is_return"):
if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
doc.consolidated_invoice = ""
doc.set('payments', [])
doc.set("payments", [])
for data in source.payments:
paid_amount = 0.00
base_paid_amount = 0.00
data.base_amount = flt(data.amount*source.conversion_rate, source.precision("base_paid_amount"))
data.base_amount = flt(
data.amount * source.conversion_rate, source.precision("base_paid_amount")
)
paid_amount += data.amount
base_paid_amount += data.base_amount
doc.append('payments', {
'mode_of_payment': data.mode_of_payment,
'type': data.type,
'amount': -1 * paid_amount,
'base_amount': -1 * base_paid_amount,
'account': data.account,
'default': data.default
})
doc.append(
"payments",
{
"mode_of_payment": data.mode_of_payment,
"type": data.type,
"amount": -1 * paid_amount,
"base_amount": -1 * base_paid_amount,
"account": data.account,
"default": data.default,
},
)
if doc.is_pos:
doc.paid_amount = -1 * source.paid_amount
elif doc.doctype == 'Purchase Invoice':
elif doc.doctype == "Purchase Invoice":
doc.paid_amount = -1 * source.paid_amount
doc.base_paid_amount = -1 * source.base_paid_amount
doc.payment_terms_template = ''
doc.payment_terms_template = ""
doc.payment_schedule = []
if doc.get("is_return") and hasattr(doc, "packed_items"):
@@ -312,16 +392,24 @@ def make_return_doc(doctype, source_name, target_doc=None):
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
if serial_nos:
target_doc.serial_no = '\n'.join(serial_nos)
target_doc.serial_no = "\n".join(serial_nos)
if doctype == "Purchase Receipt":
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype
)
target_doc.received_qty = -1 * flt(
source_doc.received_qty - (returned_qty_map.get("received_qty") or 0)
)
target_doc.rejected_qty = -1 * flt(
source_doc.rejected_qty - (returned_qty_map.get("rejected_qty") or 0)
)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
target_doc.received_stock_qty = -1 * flt(
source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0)
)
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_order_item = source_doc.purchase_order_item
@@ -329,12 +417,18 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice":
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype
)
target_doc.received_qty = -1 * flt(
source_doc.received_qty - (returned_qty_map.get("received_qty") or 0)
)
target_doc.rejected_qty = -1 * flt(
source_doc.rejected_qty - (returned_qty_map.get("rejected_qty") or 0)
)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_receipt = source_doc.purchase_receipt
target_doc.rejected_warehouse = source_doc.rejected_warehouse
@@ -343,9 +437,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.customer, source_doc.name, doctype
)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice
@@ -356,9 +452,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice" or doctype == "POS Invoice":
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.customer, source_doc.name, doctype
)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note
@@ -377,41 +475,56 @@ def make_return_doc(doctype, source_name, target_doc=None):
def update_terms(source_doc, target_doc, source_parent):
target_doc.payment_amount = -source_doc.payment_amount
doclist = get_mapped_doc(doctype, source_name, {
doctype: {
"doctype": doctype,
"validation": {
"docstatus": ["=", 1],
}
},
doctype +" Item": {
"doctype": doctype + " Item",
"field_map": {
"serial_no": "serial_no",
"batch_no": "batch_no"
doclist = get_mapped_doc(
doctype,
source_name,
{
doctype: {
"doctype": doctype,
"validation": {
"docstatus": ["=", 1],
},
},
"postprocess": update_item
doctype
+ " Item": {
"doctype": doctype + " Item",
"field_map": {"serial_no": "serial_no", "batch_no": "batch_no"},
"postprocess": update_item,
},
"Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},
},
"Payment Schedule": {
"doctype": "Payment Schedule",
"postprocess": update_terms
}
}, target_doc, set_missing_values)
target_doc,
set_missing_values,
)
doclist.set_onload('ignore_price_list', True)
doclist.set_onload("ignore_price_list", True)
return doclist
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
item_row=None, voucher_detail_no=None, sle=None):
def get_rate_for_return(
voucher_type,
voucher_no,
item_code,
return_against=None,
item_row=None,
voucher_detail_no=None,
sle=None,
):
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
return_against, item_code, return_against_item_field, item_row)
filters = get_filters(
voucher_type,
voucher_no,
voucher_detail_no,
return_against,
item_code,
return_against_item_field,
item_row,
)
if voucher_type in ("Purchase Receipt", "Purchase Invoice"):
select_field = "incoming_rate"
@@ -419,52 +532,65 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
select_field = "abs(stock_value_difference / actual_qty)"
rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]:
rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate")
if not rate and sle:
rate = get_incoming_rate({
"item_code": sle.item_code,
"warehouse": sle.warehouse,
"posting_date": sle.get('posting_date'),
"posting_time": sle.get('posting_time'),
"qty": sle.actual_qty,
"serial_no": sle.get('serial_no'),
"company": sle.company,
"voucher_type": sle.voucher_type,
"voucher_no": sle.voucher_no
}, raise_error_if_no_rate=False)
rate = get_incoming_rate(
{
"item_code": sle.item_code,
"warehouse": sle.warehouse,
"posting_date": sle.get("posting_date"),
"posting_time": sle.get("posting_time"),
"qty": sle.actual_qty,
"serial_no": sle.get("serial_no"),
"company": sle.company,
"voucher_type": sle.voucher_type,
"voucher_no": sle.voucher_no,
},
raise_error_if_no_rate=False,
)
return rate
def get_return_against_item_fields(voucher_type):
return_against_item_fields = {
"Purchase Receipt": "purchase_receipt_item",
"Purchase Invoice": "purchase_invoice_item",
"Delivery Note": "dn_detail",
"Sales Invoice": "sales_invoice_item"
"Sales Invoice": "sales_invoice_item",
}
return return_against_item_fields[voucher_type]
def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, item_code, return_against_item_field, item_row):
filters = {
"voucher_type": voucher_type,
"voucher_no": return_against,
"item_code": item_code
}
def get_filters(
voucher_type,
voucher_no,
voucher_detail_no,
return_against,
item_code,
return_against_item_field,
item_row,
):
filters = {"voucher_type": voucher_type, "voucher_no": return_against, "item_code": item_code}
if item_row:
reference_voucher_detail_no = item_row.get(return_against_item_field)
else:
reference_voucher_detail_no = frappe.db.get_value(voucher_type + " Item", voucher_detail_no, return_against_item_field)
reference_voucher_detail_no = frappe.db.get_value(
voucher_type + " Item", voucher_detail_no, return_against_item_field
)
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
return filters
def get_returned_serial_nos(child_doc, parent_doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
return_ref_field = frappe.scrub(child_doc.doctype)
if child_doc.doctype == "Delivery Note Item":
return_ref_field = "dn_detail"
@@ -473,10 +599,14 @@ def get_returned_serial_nos(child_doc, parent_doc):
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1],
[child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]]
filters = [
[parent_doc.doctype, "return_against", "=", parent_doc.name],
[parent_doc.doctype, "is_return", "=", 1],
[child_doc.doctype, return_ref_field, "=", child_doc.name],
[parent_doc.doctype, "docstatus", "=", 1],
]
for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters):
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
serial_nos.extend(get_serial_nos(row.serial_no))
return serial_nos

View File

@@ -17,8 +17,7 @@ from erpnext.stock.utils import get_incoming_rate
class SellingController(StockController):
def get_feed(self):
return _("To {0} | {1} {2}").format(self.customer_name, self.currency,
self.grand_total)
return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total)
def onload(self):
super(SellingController, self).onload()
@@ -64,32 +63,43 @@ class SellingController(StockController):
if customer:
from erpnext.accounts.party import _get_party_details
fetch_payment_terms_template = False
if (self.get("__islocal") or
self.company != frappe.db.get_value(self.doctype, self.name, 'company')):
if self.get("__islocal") or self.company != frappe.db.get_value(
self.doctype, self.name, "company"
):
fetch_payment_terms_template = True
party_details = _get_party_details(customer,
party_details = _get_party_details(
customer,
ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company,
posting_date=self.get('posting_date'),
doctype=self.doctype,
company=self.company,
posting_date=self.get("posting_date"),
fetch_payment_terms_template=fetch_payment_terms_template,
party_address=self.customer_address, shipping_address=self.shipping_address_name,
company_address=self.get('company_address'))
party_address=self.customer_address,
shipping_address=self.shipping_address_name,
company_address=self.get("company_address"),
)
if not self.meta.get_field("sales_team"):
party_details.pop("sales_team")
self.update_if_missing(party_details)
elif lead:
from erpnext.crm.doctype.lead.lead import get_lead_details
self.update_if_missing(get_lead_details(lead,
posting_date=self.get('transaction_date') or self.get('posting_date'),
company=self.company))
if self.get('taxes_and_charges') and not self.get('taxes') and not for_validate:
taxes = get_taxes_and_charges('Sales Taxes and Charges Template', self.taxes_and_charges)
self.update_if_missing(
get_lead_details(
lead,
posting_date=self.get("transaction_date") or self.get("posting_date"),
company=self.company,
)
)
if self.get("taxes_and_charges") and not self.get("taxes") and not for_validate:
taxes = get_taxes_and_charges("Sales Taxes and Charges Template", self.taxes_and_charges)
for tax in taxes:
self.append('taxes', tax)
self.append("taxes", tax)
def set_price_list_and_item_details(self, for_validate=False):
self.set_price_list_currency("Selling")
@@ -98,12 +108,15 @@ class SellingController(StockController):
def remove_shipping_charge(self):
if self.shipping_rule:
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
existing_shipping_charge = self.get("taxes", {
"doctype": "Sales Taxes and Charges",
"charge_type": "Actual",
"account_head": shipping_rule.account,
"cost_center": shipping_rule.cost_center
})
existing_shipping_charge = self.get(
"taxes",
{
"doctype": "Sales Taxes and Charges",
"charge_type": "Actual",
"account_head": shipping_rule.account,
"cost_center": shipping_rule.cost_center,
},
)
if existing_shipping_charge:
self.get("taxes").remove(existing_shipping_charge[-1])
self.calculate_taxes_and_totals()
@@ -112,8 +125,9 @@ class SellingController(StockController):
from frappe.utils import money_in_words
if self.meta.get_field("base_in_words"):
base_amount = abs(self.base_grand_total
if self.is_rounded_total_disabled() else self.base_rounded_total)
base_amount = abs(
self.base_grand_total if self.is_rounded_total_disabled() else self.base_rounded_total
)
self.base_in_words = money_in_words(base_amount, self.company_currency)
if self.meta.get_field("in_words"):
@@ -124,15 +138,15 @@ class SellingController(StockController):
if not self.meta.get_field("commission_rate"):
return
self.round_floats_in(
self, ("amount_eligible_for_commission", "commission_rate")
)
self.round_floats_in(self, ("amount_eligible_for_commission", "commission_rate"))
if not (0 <= self.commission_rate <= 100.0):
throw("{} {}".format(
_(self.meta.get_label("commission_rate")),
_("must be between 0 and 100"),
))
throw(
"{} {}".format(
_(self.meta.get_label("commission_rate")),
_("must be between 0 and 100"),
)
)
self.amount_eligible_for_commission = sum(
item.base_net_amount for item in self.items if item.grant_commission
@@ -140,7 +154,7 @@ class SellingController(StockController):
self.total_commission = flt(
self.amount_eligible_for_commission * self.commission_rate / 100.0,
self.precision("total_commission")
self.precision("total_commission"),
)
def calculate_contribution(self):
@@ -154,12 +168,14 @@ class SellingController(StockController):
sales_person.allocated_amount = flt(
self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0,
self.precision("allocated_amount", sales_person))
self.precision("allocated_amount", sales_person),
)
if sales_person.commission_rate:
sales_person.incentives = flt(
sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
self.precision("incentives", sales_person))
self.precision("incentives", sales_person),
)
total += sales_person.allocated_percentage
@@ -183,25 +199,29 @@ class SellingController(StockController):
def validate_selling_price(self):
def throw_message(idx, item_name, rate, ref_rate_field):
throw(_("""Row #{0}: Selling rate for item {1} is lower than its {2}.
throw(
_(
"""Row #{0}: Selling rate for item {1} is lower than its {2}.
Selling {3} should be atleast {4}.<br><br>Alternatively,
you can disable selling price validation in {5} to bypass
this validation.""").format(
idx,
bold(item_name),
bold(ref_rate_field),
bold("net rate"),
bold(rate),
get_link_to_form("Selling Settings", "Selling Settings"),
), title=_("Invalid Selling Price"))
this validation."""
).format(
idx,
bold(item_name),
bold(ref_rate_field),
bold("net rate"),
bold(rate),
get_link_to_form("Selling Settings", "Selling Settings"),
),
title=_("Invalid Selling Price"),
)
if (
self.get("is_return")
or not frappe.db.get_single_value("Selling Settings", "validate_selling_price")
if self.get("is_return") or not frappe.db.get_single_value(
"Selling Settings", "validate_selling_price"
):
return
is_internal_customer = self.get('is_internal_customer')
is_internal_customer = self.get("is_internal_customer")
valuation_rate_map = {}
for item in self.items:
@@ -212,17 +232,10 @@ class SellingController(StockController):
"Item", item.item_code, ("last_purchase_rate", "is_stock_item")
)
last_purchase_rate_in_sales_uom = (
last_purchase_rate * (item.conversion_factor or 1)
)
last_purchase_rate_in_sales_uom = last_purchase_rate * (item.conversion_factor or 1)
if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
throw_message(
item.idx,
item.item_name,
last_purchase_rate_in_sales_uom,
"last purchase rate"
)
throw_message(item.idx, item.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")
if is_internal_customer or not is_stock_item:
continue
@@ -238,7 +251,8 @@ class SellingController(StockController):
for valuation_rate in valuation_rate_map
)
valuation_rates = frappe.db.sql(f"""
valuation_rates = frappe.db.sql(
f"""
select
item_code, warehouse, valuation_rate
from
@@ -246,7 +260,9 @@ class SellingController(StockController):
where
({" or ".join(or_conditions)})
and valuation_rate > 0
""", as_dict=True)
""",
as_dict=True,
)
for rate in valuation_rates:
valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
@@ -255,24 +271,15 @@ class SellingController(StockController):
if not item.item_code or item.is_free_item:
continue
last_valuation_rate = valuation_rate_map.get(
(item.item_code, item.warehouse)
)
last_valuation_rate = valuation_rate_map.get((item.item_code, item.warehouse))
if not last_valuation_rate:
continue
last_valuation_rate_in_sales_uom = (
last_valuation_rate * (item.conversion_factor or 1)
)
last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
throw_message(
item.idx,
item.item_name,
last_valuation_rate_in_sales_uom,
"valuation rate"
)
throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate")
def get_item_list(self):
il = []
@@ -284,68 +291,90 @@ class SellingController(StockController):
for p in self.get("packed_items"):
if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
# the packing details table's qty is already multiplied with parent's qty
il.append(frappe._dict({
'warehouse': p.warehouse or d.warehouse,
'item_code': p.item_code,
'qty': flt(p.qty),
'uom': p.uom,
'batch_no': cstr(p.batch_no).strip(),
'serial_no': cstr(p.serial_no).strip(),
'name': d.name,
'target_warehouse': p.target_warehouse,
'company': self.company,
'voucher_type': self.doctype,
'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"),
'dn_detail': d.get("dn_detail"),
'incoming_rate': p.get("incoming_rate")
}))
il.append(
frappe._dict(
{
"warehouse": p.warehouse or d.warehouse,
"item_code": p.item_code,
"qty": flt(p.qty),
"uom": p.uom,
"batch_no": cstr(p.batch_no).strip(),
"serial_no": cstr(p.serial_no).strip(),
"name": d.name,
"target_warehouse": p.target_warehouse,
"company": self.company,
"voucher_type": self.doctype,
"allow_zero_valuation": d.allow_zero_valuation_rate,
"sales_invoice_item": d.get("sales_invoice_item"),
"dn_detail": d.get("dn_detail"),
"incoming_rate": p.get("incoming_rate"),
}
)
)
else:
il.append(frappe._dict({
'warehouse': d.warehouse,
'item_code': d.item_code,
'qty': d.stock_qty,
'uom': d.uom,
'stock_uom': d.stock_uom,
'conversion_factor': d.conversion_factor,
'batch_no': cstr(d.get("batch_no")).strip(),
'serial_no': cstr(d.get("serial_no")).strip(),
'name': d.name,
'target_warehouse': d.target_warehouse,
'company': self.company,
'voucher_type': self.doctype,
'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"),
'dn_detail': d.get("dn_detail"),
'incoming_rate': d.get("incoming_rate")
}))
il.append(
frappe._dict(
{
"warehouse": d.warehouse,
"item_code": d.item_code,
"qty": d.stock_qty,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor,
"batch_no": cstr(d.get("batch_no")).strip(),
"serial_no": cstr(d.get("serial_no")).strip(),
"name": d.name,
"target_warehouse": d.target_warehouse,
"company": self.company,
"voucher_type": self.doctype,
"allow_zero_valuation": d.allow_zero_valuation_rate,
"sales_invoice_item": d.get("sales_invoice_item"),
"dn_detail": d.get("dn_detail"),
"incoming_rate": d.get("incoming_rate"),
}
)
)
return il
def has_product_bundle(self, item_code):
return frappe.db.sql("""select name from `tabProduct Bundle`
where new_item_code=%s and docstatus != 2""", item_code)
return frappe.db.sql(
"""select name from `tabProduct Bundle`
where new_item_code=%s and docstatus != 2""",
item_code,
)
def get_already_delivered_qty(self, current_docname, so, so_detail):
delivered_via_dn = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item`
delivered_via_dn = frappe.db.sql(
"""select sum(qty) from `tabDelivery Note Item`
where so_detail = %s and docstatus = 1
and against_sales_order = %s
and parent != %s""", (so_detail, so, current_docname))
and parent != %s""",
(so_detail, so, current_docname),
)
delivered_via_si = frappe.db.sql("""select sum(si_item.qty)
delivered_via_si = frappe.db.sql(
"""select sum(si_item.qty)
from `tabSales Invoice Item` si_item, `tabSales Invoice` si
where si_item.parent = si.name and si.update_stock = 1
and si_item.so_detail = %s and si.docstatus = 1
and si_item.sales_order = %s
and si.name != %s""", (so_detail, so, current_docname))
and si.name != %s""",
(so_detail, so, current_docname),
)
total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) \
+ (flt(delivered_via_si[0][0]) if delivered_via_si else 0)
total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) + (
flt(delivered_via_si[0][0]) if delivered_via_si else 0
)
return total_delivered_qty
def get_so_qty_and_warehouse(self, so_detail):
so_item = frappe.db.sql("""select qty, warehouse from `tabSales Order Item`
where name = %s and docstatus = 1""", so_detail, as_dict=1)
so_item = frappe.db.sql(
"""select qty, warehouse from `tabSales Order Item`
where name = %s and docstatus = 1""",
so_detail,
as_dict=1,
)
so_qty = so_item and flt(so_item[0]["qty"]) or 0.0
so_warehouse = so_item and so_item[0]["warehouse"] or ""
return so_qty, so_warehouse
@@ -371,8 +400,9 @@ class SellingController(StockController):
sales_order = frappe.get_doc("Sales Order", so)
if sales_order.status in ["Closed", "Cancelled"]:
frappe.throw(_("{0} {1} is cancelled or closed").format(_("Sales Order"), so),
frappe.InvalidStatusError)
frappe.throw(
_("{0} {1} is cancelled or closed").format(_("Sales Order"), so), frappe.InvalidStatusError
)
sales_order.update_reserved_qty(so_item_rows)
@@ -384,42 +414,51 @@ class SellingController(StockController):
for d in items:
if not self.get("return_against"):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
if not (self.get("is_return") and d.incoming_rate):
d.incoming_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.get('posting_date') or self.get('transaction_date'),
"posting_time": self.get('posting_time') or nowtime(),
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
"serial_no": d.get('serial_no'),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation")
}, raise_error_if_no_rate=False)
d.incoming_rate = get_incoming_rate(
{
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.get("posting_date") or self.get("transaction_date"),
"posting_time": self.get("posting_time") or nowtime(),
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
"serial_no": d.get("serial_no"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
},
raise_error_if_no_rate=False,
)
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
if d.doctype == "Packed Item":
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision('incoming_rate'))
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
if d.rate != rate:
d.rate = rate
d.discount_percentage = 0
d.discount_amount = 0
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
.format(d.idx), alert=1)
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
elif self.get("return_against"):
# Get incoming rate of return entry from reference document
# based on original item cost as per valuation method
d.incoming_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
d.incoming_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
def update_stock_ledger(self):
self.update_reserved_qty()
@@ -428,63 +467,66 @@ class SellingController(StockController):
# Loop over items and packed items table
for d in self.get_item_list():
if frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty):
if flt(d.conversion_factor)==0.0:
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
if flt(d.conversion_factor) == 0.0:
d.conversion_factor = (
get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
)
# On cancellation or return entry submission, make stock ledger entry for
# target warehouse first, to update serial no values properly
if d.warehouse and ((not cint(self.is_return) and self.docstatus==1)
or (cint(self.is_return) and self.docstatus==2)):
sl_entries.append(self.get_sle_for_source_warehouse(d))
if d.warehouse and (
(not cint(self.is_return) and self.docstatus == 1)
or (cint(self.is_return) and self.docstatus == 2)
):
sl_entries.append(self.get_sle_for_source_warehouse(d))
if d.target_warehouse:
sl_entries.append(self.get_sle_for_target_warehouse(d))
if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
or (cint(self.is_return) and self.docstatus==1)):
sl_entries.append(self.get_sle_for_source_warehouse(d))
if d.warehouse and (
(not cint(self.is_return) and self.docstatus == 2)
or (cint(self.is_return) and self.docstatus == 1)
):
sl_entries.append(self.get_sle_for_source_warehouse(d))
self.make_sl_entries(sl_entries)
def get_sle_for_source_warehouse(self, item_row):
sle = self.get_sl_entries(item_row, {
"actual_qty": -1*flt(item_row.qty),
"incoming_rate": item_row.incoming_rate,
"recalculate_rate": cint(self.is_return)
})
sle = self.get_sl_entries(
item_row,
{
"actual_qty": -1 * flt(item_row.qty),
"incoming_rate": item_row.incoming_rate,
"recalculate_rate": cint(self.is_return),
},
)
if item_row.target_warehouse and not cint(self.is_return):
sle.dependant_sle_voucher_detail_no = item_row.name
return sle
def get_sle_for_target_warehouse(self, item_row):
sle = self.get_sl_entries(item_row, {
"actual_qty": flt(item_row.qty),
"warehouse": item_row.target_warehouse
})
sle = self.get_sl_entries(
item_row, {"actual_qty": flt(item_row.qty), "warehouse": item_row.target_warehouse}
)
if self.docstatus == 1:
if not cint(self.is_return):
sle.update({
"incoming_rate": item_row.incoming_rate,
"recalculate_rate": 1
})
sle.update({"incoming_rate": item_row.incoming_rate, "recalculate_rate": 1})
else:
sle.update({
"outgoing_rate": item_row.incoming_rate
})
sle.update({"outgoing_rate": item_row.incoming_rate})
if item_row.warehouse:
sle.dependant_sle_voucher_detail_no = item_row.name
return sle
def set_po_nos(self, for_validate=False):
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
if self.doctype == "Sales Invoice" and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_sales_invoice()
if self.doctype == 'Delivery Note' and hasattr(self, "items"):
if self.doctype == "Delivery Note" and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_delivery_note()
@@ -493,34 +535,39 @@ class SellingController(StockController):
po_nos = []
if self.po_no:
po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'sales_order', po_nos)
self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
self.get_po_nos("Sales Order", "sales_order", po_nos)
self.get_po_nos("Delivery Note", "delivery_note", po_nos)
self.po_no = ", ".join(list(set(x.strip() for x in ",".join(po_nos).split(","))))
def set_pos_for_delivery_note(self):
po_nos = []
if self.po_no:
po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
self.get_po_nos("Sales Order", "against_sales_order", po_nos)
self.get_po_nos("Sales Invoice", "against_sales_invoice", po_nos)
self.po_no = ", ".join(list(set(x.strip() for x in ",".join(po_nos).split(","))))
def get_po_nos(self, ref_doctype, ref_fieldname, po_nos):
doc_list = list(set(d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)))
if doc_list:
po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')]
po_nos += [
d.po_no
for d in frappe.get_all(ref_doctype, "po_no", filters={"name": ("in", doc_list)})
if d.get("po_no")
]
def set_gross_profit(self):
if self.doctype in ["Sales Order", "Quotation"]:
for item in self.items:
item.gross_profit = flt(((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item))
item.gross_profit = flt(
((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
)
def set_customer_address(self):
address_dict = {
'customer_address': 'address_display',
'shipping_address_name': 'shipping_address',
'company_address': 'company_address_display'
"customer_address": "address_display",
"shipping_address_name": "shipping_address",
"company_address": "company_address_display",
}
for address_field, address_display_field in address_dict.items():
@@ -536,15 +583,31 @@ class SellingController(StockController):
if self.doctype == "POS Invoice":
return
for d in self.get('items'):
for d in self.get("items"):
if self.doctype == "Sales Invoice":
stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
stock_items = [
d.item_code,
d.description,
d.warehouse,
d.sales_order or d.delivery_note,
d.batch_no or "",
]
non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
elif self.doctype == "Delivery Note":
stock_items = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
non_stock_items = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
stock_items = [
d.item_code,
d.description,
d.warehouse,
d.against_sales_order or d.against_sales_invoice,
d.batch_no or "",
]
non_stock_items = [
d.item_code,
d.description,
d.against_sales_order or d.against_sales_invoice,
]
elif self.doctype in ["Sales Order", "Quotation"]:
stock_items = [d.item_code, d.description, d.warehouse, '']
stock_items = [d.item_code, d.description, d.warehouse, ""]
non_stock_items = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
@@ -552,7 +615,7 @@ class SellingController(StockController):
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")
get_link_to_form("Selling Settings", "Selling Settings"),
)
if stock_items in check_list:
frappe.throw(duplicate_items_msg)
@@ -570,22 +633,26 @@ class SellingController(StockController):
for d in items:
if d.get("target_warehouse") and d.get("warehouse") == d.get("target_warehouse"):
warehouse = frappe.bold(d.get("target_warehouse"))
frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same")
.format(d.idx, warehouse, warehouse))
frappe.throw(
_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same").format(
d.idx, warehouse, warehouse
)
)
if not self.get("is_internal_customer") and any(d.get("target_warehouse") for d in items):
msg = _("Target Warehouse is set for some items but the customer is not an internal customer.")
msg += " " + _("This {} will be treated as material transfer.").format(_(self.doctype))
frappe.msgprint(msg, title="Internal Transfer", alert=True)
def validate_items(self):
# validate items to see if they have is_sales_item enabled
from erpnext.controllers.buying_controller import validate_item_type
validate_item_type(self, "is_sales_item", "sales")
def set_default_income_account_for_item(obj):
for d in obj.get("items"):
if d.item_code:
if getattr(d, "income_account", None):
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
set_item_default(d.item_code, obj.company, "income_account", d.income_account)

View File

@@ -8,12 +8,15 @@ from frappe.model.document import Document
from frappe.utils import comma_or, flt, getdate, now, nowdate
class OverAllowanceError(frappe.ValidationError): pass
class OverAllowanceError(frappe.ValidationError):
pass
def validate_status(status, options):
if status not in options:
frappe.throw(_("Status must be one of {0}").format(comma_or(options)))
status_map = {
"Lead": [
["Lost Quotation", "has_lost_quotation"],
@@ -26,7 +29,7 @@ status_map = {
["Lost", "has_lost_quotation"],
["Quotation", "has_active_quotation"],
["Converted", "has_ordered_quotation"],
["Closed", "eval:self.status=='Closed'"]
["Closed", "eval:self.status=='Closed'"],
],
"Quotation": [
["Draft", None],
@@ -37,20 +40,41 @@ status_map = {
],
"Sales Order": [
["Draft", None],
["To Deliver and Bill", "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Bill", "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1"],
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note"],
["Completed", "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1"],
[
"To Deliver and Bill",
"eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1",
],
[
"To Bill",
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
],
[
"To Deliver",
"eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note",
],
[
"Completed",
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["On Hold", "eval:self.status=='On Hold'"],
],
"Purchase Order": [
["Draft", None],
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
[
"To Receive and Bill",
"eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1",
],
["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1"],
[
"To Receive",
"eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1",
],
[
"Completed",
"eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1",
],
["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"],
@@ -77,18 +101,39 @@ status_map = {
["Stopped", "eval:self.status == 'Stopped'"],
["Cancelled", "eval:self.docstatus == 2"],
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
[
"Ordered",
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
],
[
"Transferred",
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'",
],
[
"Issued",
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'",
],
[
"Received",
"eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
],
[
"Partially Received",
"eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
],
[
"Partially Ordered",
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1",
],
[
"Manufactured",
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
],
],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"],
["Cancelled", "eval:self.docstatus == 2"]
["Cancelled", "eval:self.docstatus == 2"],
],
"POS Opening Entry": [
["Draft", None],
@@ -106,15 +151,16 @@ status_map = {
"Transaction Deletion Record": [
["Draft", None],
["Completed", "eval:self.docstatus == 1"],
]
],
}
class StatusUpdater(Document):
"""
Updates the status of the calling records
Delivery Note: Update Delivered Qty, Update Percent and Validate over delivery
Sales Invoice: Update Billed Amt, Update Percent and Validate over billing
Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
Updates the status of the calling records
Delivery Note: Update Delivered Qty, Update Percent and Validate over delivery
Sales Invoice: Update Billed Amt, Update Percent and Validate over billing
Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
"""
def update_prevdoc_status(self):
@@ -123,8 +169,8 @@ class StatusUpdater(Document):
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
if self.get('amended_from'):
self.status = 'Draft'
if self.get("amended_from"):
self.status = "Draft"
return
if self.doctype in status_map:
@@ -139,20 +185,33 @@ class StatusUpdater(Document):
self.status = s[0]
break
elif s[1].startswith("eval:"):
if frappe.safe_eval(s[1][5:], None, { "self": self.as_dict(), "getdate": getdate,
"nowdate": nowdate, "get_value": frappe.db.get_value }):
if frappe.safe_eval(
s[1][5:],
None,
{
"self": self.as_dict(),
"getdate": getdate,
"nowdate": nowdate,
"get_value": frappe.db.get_value,
},
):
self.status = s[0]
break
elif getattr(self, s[1])():
self.status = s[0]
break
if self.status != _status and self.status not in ("Cancelled", "Partially Ordered",
"Ordered", "Issued", "Transferred"):
if self.status != _status and self.status not in (
"Cancelled",
"Partially Ordered",
"Ordered",
"Issued",
"Transferred",
):
self.add_comment("Label", _(self.status))
if update:
self.db_set('status', self.status, update_modified = update_modified)
self.db_set("status", self.status, update_modified=update_modified)
def validate_qty(self):
"""Validates qty at row level"""
@@ -167,57 +226,78 @@ class StatusUpdater(Document):
# get unique transactions to update
for d in self.get_all_children():
if hasattr(d, 'qty') and d.qty < 0 and not self.get('is_return'):
if hasattr(d, "qty") and d.qty < 0 and not self.get("is_return"):
frappe.throw(_("For an item {0}, quantity must be positive number").format(d.item_code))
if hasattr(d, 'qty') and d.qty > 0 and self.get('is_return'):
if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"):
frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code))
if d.doctype == args['source_dt'] and d.get(args["join_field"]):
args['name'] = d.get(args['join_field'])
if d.doctype == args["source_dt"] and d.get(args["join_field"]):
args["name"] = d.get(args["join_field"])
# get all qty where qty > target_field
item = frappe.db.sql("""select item_code, `{target_ref_field}`,
item = frappe.db.sql(
"""select item_code, `{target_ref_field}`,
`{target_field}`, parenttype, parent from `tab{target_dt}`
where `{target_ref_field}` < `{target_field}`
and name=%s and docstatus=1""".format(**args),
args['name'], as_dict=1)
and name=%s and docstatus=1""".format(
**args
),
args["name"],
as_dict=1,
)
if item:
item = item[0]
item['idx'] = d.idx
item['target_ref_field'] = args['target_ref_field'].replace('_', ' ')
item["idx"] = d.idx
item["target_ref_field"] = args["target_ref_field"].replace("_", " ")
# if not item[args['target_ref_field']]:
# msgprint(_("Note: System will not check over-delivery and over-booking for Item {0} as quantity or amount is 0").format(item.item_code))
if args.get('no_allowance'):
item['reduce_by'] = item[args['target_field']] - item[args['target_ref_field']]
if item['reduce_by'] > .01:
if args.get("no_allowance"):
item["reduce_by"] = item[args["target_field"]] - item[args["target_ref_field"]]
if item["reduce_by"] > 0.01:
self.limits_crossed_error(args, item, "qty")
elif item[args['target_ref_field']]:
elif item[args["target_ref_field"]]:
self.check_overflow_with_allowance(item, args)
def check_overflow_with_allowance(self, item, args):
"""
Checks if there is overflow condering a relaxation allowance
Checks if there is overflow condering a relaxation allowance
"""
qty_or_amount = "qty" if "qty" in args['target_ref_field'] else "amount"
qty_or_amount = "qty" if "qty" in args["target_ref_field"] else "amount"
# check if overflow is within allowance
allowance, self.item_allowance, self.global_qty_allowance, self.global_amount_allowance = \
get_allowance_for(item['item_code'], self.item_allowance,
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
(
allowance,
self.item_allowance,
self.global_qty_allowance,
self.global_amount_allowance,
) = get_allowance_for(
item["item_code"],
self.item_allowance,
self.global_qty_allowance,
self.global_amount_allowance,
qty_or_amount,
)
role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
role_allowed_to_over_deliver_receive = frappe.db.get_single_value(
"Stock Settings", "role_allowed_to_over_deliver_receive"
)
role_allowed_to_over_bill = frappe.db.get_single_value(
"Accounts Settings", "role_allowed_to_over_bill"
)
role = (
role_allowed_to_over_deliver_receive if qty_or_amount == "qty" else role_allowed_to_over_bill
)
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
item[args['target_ref_field']]) * 100
overflow_percent = (
(item[args["target_field"]] - item[args["target_ref_field"]]) / item[args["target_ref_field"]]
) * 100
if overflow_percent - allowance > 0.01:
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
item["max_allowed"] = flt(item[args["target_ref_field"]] * (100 + allowance) / 100)
item["reduce_by"] = item[args["target_field"]] - item["max_allowed"]
if role not in frappe.get_roles():
self.limits_crossed_error(args, item, qty_or_amount)
@@ -225,45 +305,55 @@ class StatusUpdater(Document):
self.warn_about_bypassing_with_role(item, qty_or_amount, role)
def limits_crossed_error(self, args, item, qty_or_amount):
'''Raise exception for limits crossed'''
"""Raise exception for limits crossed"""
if qty_or_amount == "qty":
action_msg = _('To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.')
action_msg = _(
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
)
else:
action_msg = _('To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.')
action_msg = _(
'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'
)
frappe.throw(_('This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?')
.format(
frappe.throw(
_(
"This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?"
).format(
frappe.bold(_(item["target_ref_field"].title())),
frappe.bold(item["reduce_by"]),
frappe.bold(_(args.get('target_dt'))),
frappe.bold(_(args.get("target_dt"))),
frappe.bold(_(self.doctype)),
frappe.bold(item.get('item_code'))
) + '<br><br>' + action_msg, OverAllowanceError, title = _('Limit Crossed'))
frappe.bold(item.get("item_code")),
)
+ "<br><br>"
+ action_msg,
OverAllowanceError,
title=_("Limit Crossed"),
)
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
msg = (_("{} of {} {} ignored for item {} because you have {} role.")
.format(
action,
_(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]),
frappe.bold(item.get('item_code')),
role)
)
msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
action,
_(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]),
frappe.bold(item.get("item_code")),
role,
)
frappe.msgprint(msg, indicator="orange", alert=True)
def update_qty(self, update_modified=True):
"""Updates qty or amount at row level
:param update_modified: If true, updates `modified` and `modified_by` for target parent doc
:param update_modified: If true, updates `modified` and `modified_by` for target parent doc
"""
for args in self.status_updater:
# condition to include current record (if submit or no if cancel)
if self.docstatus == 1:
args['cond'] = ' or parent="%s"' % self.name.replace('"', '\"')
args["cond"] = ' or parent="%s"' % self.name.replace('"', '"')
else:
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
args["cond"] = ' and parent!="%s"' % self.name.replace('"', '"')
self._update_children(args, update_modified)
@@ -273,56 +363,73 @@ class StatusUpdater(Document):
def _update_children(self, args, update_modified):
"""Update quantities or amount in child table"""
for d in self.get_all_children():
if d.doctype != args['source_dt']:
if d.doctype != args["source_dt"]:
continue
self._update_modified(args, update_modified)
# updates qty in the child table
args['detail_id'] = d.get(args['join_field'])
args["detail_id"] = d.get(args["join_field"])
args['second_source_condition'] = ""
if args.get('second_source_dt') and args.get('second_source_field') \
and args.get('second_join_field'):
args["second_source_condition"] = ""
if (
args.get("second_source_dt")
and args.get("second_source_field")
and args.get("second_join_field")
):
if not args.get("second_source_extra_cond"):
args["second_source_extra_cond"] = ""
args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
args["second_source_condition"] = frappe.db.sql(
""" select ifnull((select sum(%(second_source_field)s)
from `tab%(second_source_dt)s`
where `%(second_join_field)s`="%(detail_id)s"
and (`tab%(second_source_dt)s`.docstatus=1)
%(second_source_extra_cond)s), 0) """ % args)[0][0]
%(second_source_extra_cond)s), 0) """
% args
)[0][0]
if args['detail_id']:
if not args.get("extra_cond"): args["extra_cond"] = ""
if args["detail_id"]:
if not args.get("extra_cond"):
args["extra_cond"] = ""
args["source_dt_value"] = frappe.db.sql("""
args["source_dt_value"] = (
frappe.db.sql(
"""
(select ifnull(sum(%(source_field)s), 0)
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
and (docstatus=1 %(cond)s) %(extra_cond)s)
""" % args)[0][0] or 0.0
"""
% args
)[0][0]
or 0.0
)
if args['second_source_condition']:
args["source_dt_value"] += flt(args['second_source_condition'])
if args["second_source_condition"]:
args["source_dt_value"] += flt(args["second_source_condition"])
frappe.db.sql("""update `tab%(target_dt)s`
frappe.db.sql(
"""update `tab%(target_dt)s`
set %(target_field)s = %(source_dt_value)s %(update_modified)s
where name='%(detail_id)s'""" % args)
where name='%(detail_id)s'"""
% args
)
def _update_percent_field_in_targets(self, args, update_modified=True):
"""Update percent field in parent transaction"""
if args.get('percent_join_field_parent'):
if args.get("percent_join_field_parent"):
# if reference to target doc where % is to be updated, is
# in source doc's parent form, consider percent_join_field_parent
args['name'] = self.get(args['percent_join_field_parent'])
args["name"] = self.get(args["percent_join_field_parent"])
self._update_percent_field(args, update_modified)
else:
distinct_transactions = set(d.get(args['percent_join_field'])
for d in self.get_all_children(args['source_dt']))
distinct_transactions = set(
d.get(args["percent_join_field"]) for d in self.get_all_children(args["source_dt"])
)
for name in distinct_transactions:
if name:
args['name'] = name
args["name"] = name
self._update_percent_field(args, update_modified)
def _update_percent_field(self, args, update_modified=True):
@@ -330,23 +437,29 @@ class StatusUpdater(Document):
self._update_modified(args, update_modified)
if args.get('target_parent_field'):
frappe.db.sql("""update `tab%(target_parent_dt)s`
if args.get("target_parent_field"):
frappe.db.sql(
"""update `tab%(target_parent_dt)s`
set %(target_parent_field)s = round(
ifnull((select
ifnull(sum(if(abs(%(target_ref_field)s) > abs(%(target_field)s), abs(%(target_field)s), abs(%(target_ref_field)s))), 0)
/ sum(abs(%(target_ref_field)s)) * 100
from `tab%(target_dt)s` where parent="%(name)s" having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
%(update_modified)s
where name='%(name)s'""" % args)
where name='%(name)s'"""
% args
)
# update field
if args.get('status_field'):
frappe.db.sql("""update `tab%(target_parent_dt)s`
if args.get("status_field"):
frappe.db.sql(
"""update `tab%(target_parent_dt)s`
set %(status_field)s = if(%(target_parent_field)s<0.001,
'Not %(keyword)s', if(%(target_parent_field)s>=99.999999,
'Fully %(keyword)s', 'Partly %(keyword)s'))
where name='%(name)s'""" % args)
where name='%(name)s'"""
% args
)
if update_modified:
target = frappe.get_doc(args["target_parent_dt"], args["name"])
@@ -355,22 +468,24 @@ class StatusUpdater(Document):
def _update_modified(self, args, update_modified):
if not update_modified:
args['update_modified'] = ''
args["update_modified"] = ""
return
args['update_modified'] = ', modified = {0}, modified_by = {1}'.format(
frappe.db.escape(now()),
frappe.db.escape(frappe.session.user)
args["update_modified"] = ", modified = {0}, modified_by = {1}".format(
frappe.db.escape(now()), frappe.db.escape(frappe.session.user)
)
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
ref_fieldname = frappe.scrub(ref_dt)
ref_docs = [item.get(ref_fieldname) for item in (self.get('items') or []) if item.get(ref_fieldname)]
ref_docs = [
item.get(ref_fieldname) for item in (self.get("items") or []) if item.get(ref_fieldname)
]
if not ref_docs:
return
zero_amount_refdocs = frappe.db.sql_list("""
zero_amount_refdocs = frappe.db.sql_list(
"""
SELECT
name
from
@@ -379,21 +494,34 @@ class StatusUpdater(Document):
docstatus = 1
and base_net_total = 0
and name in %(ref_docs)s
""".format(ref_dt=ref_dt), {
'ref_docs': ref_docs
})
""".format(
ref_dt=ref_dt
),
{"ref_docs": ref_docs},
)
if zero_amount_refdocs:
self.update_billing_status(zero_amount_refdocs, ref_dt, ref_fieldname)
def update_billing_status(self, zero_amount_refdoc, ref_dt, ref_fieldname):
for ref_dn in zero_amount_refdoc:
ref_doc_qty = flt(frappe.db.sql("""select ifnull(sum(qty), 0) from `tab%s Item`
where parent=%s""" % (ref_dt, '%s'), (ref_dn))[0][0])
ref_doc_qty = flt(
frappe.db.sql(
"""select ifnull(sum(qty), 0) from `tab%s Item`
where parent=%s"""
% (ref_dt, "%s"),
(ref_dn),
)[0][0]
)
billed_qty = flt(frappe.db.sql("""select ifnull(sum(qty), 0)
from `tab%s Item` where %s=%s and docstatus=1""" %
(self.doctype, ref_fieldname, '%s'), (ref_dn))[0][0])
billed_qty = flt(
frappe.db.sql(
"""select ifnull(sum(qty), 0)
from `tab%s Item` where %s=%s and docstatus=1"""
% (self.doctype, ref_fieldname, "%s"),
(ref_dn),
)[0][0]
)
per_billed = (min(ref_doc_qty, billed_qty) / ref_doc_qty) * 100
@@ -402,7 +530,7 @@ class StatusUpdater(Document):
ref_doc.db_set("per_billed", per_billed)
# set billling status
if hasattr(ref_doc, 'billing_status'):
if hasattr(ref_doc, "billing_status"):
if ref_doc.per_billed < 0.001:
ref_doc.db_set("billing_status", "Not Billed")
elif ref_doc.per_billed > 99.999999:
@@ -412,29 +540,51 @@ class StatusUpdater(Document):
ref_doc.set_status(update=True)
def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
def get_allowance_for(
item_code,
item_allowance=None,
global_qty_allowance=None,
global_amount_allowance=None,
qty_or_amount="qty",
):
"""
Returns the allowance for the item, if not set, returns global allowance
Returns the allowance for the item, if not set, returns global allowance
"""
if item_allowance is None:
item_allowance = {}
if qty_or_amount == "qty":
if item_allowance.get(item_code, frappe._dict()).get("qty"):
return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance
return (
item_allowance[item_code].qty,
item_allowance,
global_qty_allowance,
global_amount_allowance,
)
else:
if item_allowance.get(item_code, frappe._dict()).get("amount"):
return item_allowance[item_code].amount, item_allowance, global_qty_allowance, global_amount_allowance
return (
item_allowance[item_code].amount,
item_allowance,
global_qty_allowance,
global_amount_allowance,
)
qty_allowance, over_billing_allowance = \
frappe.db.get_value('Item', item_code, ['over_delivery_receipt_allowance', 'over_billing_allowance'])
qty_allowance, over_billing_allowance = frappe.db.get_value(
"Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"]
)
if qty_or_amount == "qty" and not qty_allowance:
if global_qty_allowance == None:
global_qty_allowance = flt(frappe.db.get_single_value('Stock Settings', 'over_delivery_receipt_allowance'))
global_qty_allowance = flt(
frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
)
qty_allowance = global_qty_allowance
elif qty_or_amount == "amount" and not over_billing_allowance:
if global_amount_allowance == None:
global_amount_allowance = flt(frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance'))
global_amount_allowance = flt(
frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
)
over_billing_allowance = global_amount_allowance
if qty_or_amount == "qty":

View File

@@ -21,14 +21,22 @@ from erpnext.stock import get_warehouse_account_map
from erpnext.stock.stock_ledger import get_items_to_be_repost
class QualityInspectionRequiredError(frappe.ValidationError): pass
class QualityInspectionRejectedError(frappe.ValidationError): pass
class QualityInspectionNotSubmittedError(frappe.ValidationError): pass
class QualityInspectionRequiredError(frappe.ValidationError):
pass
class QualityInspectionRejectedError(frappe.ValidationError):
pass
class QualityInspectionNotSubmittedError(frappe.ValidationError):
pass
class StockController(AccountsController):
def validate(self):
super(StockController, self).validate()
if not self.get('is_return'):
if not self.get("is_return"):
self.validate_inspection()
self.validate_serialized_batch()
self.clean_serial_nos()
@@ -41,44 +49,56 @@ class StockController(AccountsController):
if self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
provisional_accounting_for_non_stock_items = \
cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
)
)
if cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items:
if (
cint(erpnext.is_perpetual_inventory_enabled(self.company))
or provisional_accounting_for_non_stock_items
):
warehouse_account = get_warehouse_account_map(self.company)
if self.docstatus==1:
if self.docstatus == 1:
if not gl_entries:
gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries, from_repost=from_repost)
elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for d in self.get("items"):
if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
serial_nos = frappe.get_all("Serial No",
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
serial_nos = frappe.get_all(
"Serial No",
fields=["batch_no", "name", "warehouse"],
filters={
"name": ("in", get_serial_nos(d.serial_no))
}
filters={"name": ("in", get_serial_nos(d.serial_no))},
)
for row in serial_nos:
if row.warehouse and row.batch_no != d.batch_no:
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
.format(d.idx, row.name, d.batch_no))
frappe.throw(
_("Row #{0}: Serial No {1} does not belong to Batch {2}").format(
d.idx, row.name, d.batch_no
)
)
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):
frappe.throw(_("Row #{0}: The batch {1} has already expired.")
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
frappe.throw(
_("Row #{0}: The batch {1} has already expired.").format(
d.idx, get_link_to_form("Batch", d.get("batch_no"))
)
)
def clean_serial_nos(self):
from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string
@@ -88,13 +108,14 @@ class StockController(AccountsController):
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
for row in self.get('packed_items') or []:
for row in self.get("packed_items") or []:
if hasattr(row, "serial_no") and row.serial_no:
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None):
def get_gl_entries(
self, warehouse_account=None, default_expense_account=None, default_cost_center=None
):
if not warehouse_account:
warehouse_account = get_warehouse_account_map(self.company)
@@ -116,44 +137,61 @@ class StockController(AccountsController):
self.check_expense_account(item_row)
# expense account/ target_warehouse / source_warehouse
if item_row.get('target_warehouse'):
warehouse = item_row.get('target_warehouse')
if item_row.get("target_warehouse"):
warehouse = item_row.get("target_warehouse")
expense_account = warehouse_account[warehouse]["account"]
else:
expense_account = item_row.expense_account
gl_list.append(self.get_gl_dict({
"account": warehouse_account[sle.warehouse]["account"],
"against": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(sle.stock_value_difference, precision),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
gl_list.append(
self.get_gl_dict(
{
"account": warehouse_account[sle.warehouse]["account"],
"against": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(sle.stock_value_difference, precision),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
},
warehouse_account[sle.warehouse]["account_currency"],
item=item_row,
)
)
gl_list.append(self.get_gl_dict({
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
}, item=item_row))
gl_list.append(
self.get_gl_dict(
{
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
},
item=item_row,
)
)
elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse)
if warehouse_with_no_account:
for wh in warehouse_with_no_account:
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))
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, 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")
frappe.flags.debit_field_precision = frappe.get_precision(
"GL Entry", "debit_in_account_currency"
)
return frappe.flags.debit_field_precision
@@ -163,12 +201,16 @@ class StockController(AccountsController):
is_opening = "Yes" if reconciliation_purpose == "Opening Stock" else "No"
details = []
for voucher_detail_no in sle_map:
details.append(frappe._dict({
"name": voucher_detail_no,
"expense_account": default_expense_account,
"cost_center": default_cost_center,
"is_opening": is_opening
}))
details.append(
frappe._dict(
{
"name": voucher_detail_no,
"expense_account": default_expense_account,
"cost_center": default_cost_center,
"is_opening": is_opening,
}
)
)
return details
else:
details = self.get("items")
@@ -207,7 +249,8 @@ class StockController(AccountsController):
def get_stock_ledger_details(self):
stock_ledger = {}
stock_ledger_entries = frappe.db.sql("""
stock_ledger_entries = frappe.db.sql(
"""
select
name, warehouse, stock_value_difference, valuation_rate,
voucher_detail_no, item_code, posting_date, posting_time,
@@ -216,110 +259,154 @@ class StockController(AccountsController):
`tabStock Ledger Entry`
where
voucher_type=%s and voucher_no=%s and is_cancelled = 0
""", (self.doctype, self.name), as_dict=True)
""",
(self.doctype, self.name),
as_dict=True,
)
for sle in stock_ledger_entries:
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
return stock_ledger
def make_batches(self, warehouse_field):
'''Create batches if required. Called before submit'''
"""Create batches if required. Called before submit"""
for d in self.items:
if d.get(warehouse_field) and not d.batch_no:
has_batch_no, create_new_batch = frappe.db.get_value('Item', d.item_code, ['has_batch_no', 'create_new_batch'])
has_batch_no, create_new_batch = frappe.db.get_value(
"Item", d.item_code, ["has_batch_no", "create_new_batch"]
)
if has_batch_no and create_new_batch:
d.batch_no = frappe.get_doc(dict(
doctype='Batch',
item=d.item_code,
supplier=getattr(self, 'supplier', None),
reference_doctype=self.doctype,
reference_name=self.name)).insert().name
d.batch_no = (
frappe.get_doc(
dict(
doctype="Batch",
item=d.item_code,
supplier=getattr(self, "supplier", None),
reference_doctype=self.doctype,
reference_name=self.name,
)
)
.insert()
.name
)
def check_expense_account(self, item):
if not item.get("expense_account"):
msg = _("Please set an Expense Account in the Items table")
frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
frappe.throw(
_("Row #{0}: Expense Account not set for the Item {1}. {2}").format(
item.idx, frappe.bold(item.item_code), msg
),
title=_("Expense Account Missing"),
)
else:
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")
.format(item.get("expense_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").format(
item.get("expense_account")
)
)
if is_expense_account and not item.get("cost_center"):
frappe.throw(_("{0} {1}: Cost Center is mandatory for Item {2}").format(
_(self.doctype), self.name, item.get("item_code")))
frappe.throw(
_("{0} {1}: Cost Center is mandatory for Item {2}").format(
_(self.doctype), self.name, item.get("item_code")
)
)
def delete_auto_created_batches(self):
for d in self.items:
if not d.batch_no: continue
if not d.batch_no:
continue
frappe.db.set_value("Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None)
frappe.db.set_value(
"Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None
)
d.batch_no = None
d.db_set("batch_no", None)
for data in frappe.get_all("Batch",
{'reference_name': self.name, 'reference_doctype': self.doctype}):
for data in frappe.get_all(
"Batch", {"reference_name": self.name, "reference_doctype": self.doctype}
):
frappe.delete_doc("Batch", data.name)
def get_sl_entries(self, d, args):
sl_dict = frappe._dict({
"item_code": d.get("item_code", None),
"warehouse": d.get("warehouse", None),
"posting_date": self.posting_date,
"posting_time": self.posting_time,
'fiscal_year': get_fiscal_year(self.posting_date, company=self.company)[0],
"voucher_type": self.doctype,
"voucher_no": self.name,
"voucher_detail_no": d.name,
"actual_qty": (self.docstatus==1 and 1 or -1)*flt(d.get("stock_qty")),
"stock_uom": frappe.db.get_value("Item", args.get("item_code") or d.get("item_code"), "stock_uom"),
"incoming_rate": 0,
"company": self.company,
"batch_no": cstr(d.get("batch_no")).strip(),
"serial_no": d.get("serial_no"),
"project": d.get("project") or self.get('project'),
"is_cancelled": 1 if self.docstatus==2 else 0
})
sl_dict = frappe._dict(
{
"item_code": d.get("item_code", None),
"warehouse": d.get("warehouse", None),
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"fiscal_year": get_fiscal_year(self.posting_date, company=self.company)[0],
"voucher_type": self.doctype,
"voucher_no": self.name,
"voucher_detail_no": d.name,
"actual_qty": (self.docstatus == 1 and 1 or -1) * flt(d.get("stock_qty")),
"stock_uom": frappe.db.get_value(
"Item", args.get("item_code") or d.get("item_code"), "stock_uom"
),
"incoming_rate": 0,
"company": self.company,
"batch_no": cstr(d.get("batch_no")).strip(),
"serial_no": d.get("serial_no"),
"project": d.get("project") or self.get("project"),
"is_cancelled": 1 if self.docstatus == 2 else 0,
}
)
sl_dict.update(args)
return sl_dict
def make_sl_entries(self, sl_entries, allow_negative_stock=False,
via_landed_cost_voucher=False):
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
def make_gl_entries_on_cancel(self):
if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""", (self.doctype, self.name)):
self.make_gl_entries()
if frappe.db.sql(
"""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""",
(self.doctype, self.name),
):
self.make_gl_entries()
def get_serialized_items(self):
serialized_items = []
item_codes = list(set(d.item_code for d in self.get("items")))
if item_codes:
serialized_items = frappe.db.sql_list("""select name from `tabItem`
where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
tuple(item_codes))
serialized_items = frappe.db.sql_list(
"""select name from `tabItem`
where has_serial_no=1 and name in ({})""".format(
", ".join(["%s"] * len(item_codes))
),
tuple(item_codes),
)
return serialized_items
def validate_warehouse(self):
from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
warehouses = list(set(d.warehouse for d in
self.get("items") if getattr(d, "warehouse", None)))
warehouses = list(set(d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)))
target_warehouses = list(set([d.target_warehouse for d in
self.get("items") if getattr(d, "target_warehouse", None)]))
target_warehouses = list(
set([d.target_warehouse for d in self.get("items") if getattr(d, "target_warehouse", None)])
)
warehouses.extend(target_warehouses)
from_warehouse = list(set([d.from_warehouse for d in
self.get("items") if getattr(d, "from_warehouse", None)]))
from_warehouse = list(
set([d.from_warehouse for d in self.get("items") if getattr(d, "from_warehouse", None)])
)
warehouses.extend(from_warehouse)
@@ -332,14 +419,17 @@ class StockController(AccountsController):
if self.doctype == "Delivery Note":
target_ref_field = "amount - (returned_qty * rate)"
self._update_percent_field({
"target_dt": self.doctype + " Item",
"target_parent_dt": self.doctype,
"target_parent_field": "per_billed",
"target_ref_field": target_ref_field,
"target_field": "billed_amt",
"name": self.name,
}, update_modified)
self._update_percent_field(
{
"target_dt": self.doctype + " Item",
"target_parent_dt": self.doctype,
"target_parent_field": "per_billed",
"target_ref_field": target_ref_field,
"target_field": "billed_amt",
"name": self.name,
},
update_modified,
)
def validate_inspection(self):
"""Checks if quality inspection is set/ is valid for Items that require inspection."""
@@ -347,24 +437,28 @@ class StockController(AccountsController):
"Purchase Receipt": "inspection_required_before_purchase",
"Purchase Invoice": "inspection_required_before_purchase",
"Sales Invoice": "inspection_required_before_delivery",
"Delivery Note": "inspection_required_before_delivery"
"Delivery Note": "inspection_required_before_delivery",
}
inspection_required_fieldname = inspection_fieldname_map.get(self.doctype)
# return if inspection is not required on document level
if ((not inspection_required_fieldname and self.doctype != "Stock Entry") or
(self.doctype == "Stock Entry" and not self.inspection_required) or
(self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)):
return
if (
(not inspection_required_fieldname and self.doctype != "Stock Entry")
or (self.doctype == "Stock Entry" and not self.inspection_required)
or (self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)
):
return
for row in self.get('items'):
for row in self.get("items"):
qi_required = False
if (inspection_required_fieldname and frappe.db.get_value("Item", row.item_code, inspection_required_fieldname)):
if inspection_required_fieldname and frappe.db.get_value(
"Item", row.item_code, inspection_required_fieldname
):
qi_required = True
elif self.doctype == "Stock Entry" and row.t_warehouse:
qi_required = True # inward stock needs inspection
qi_required = True # inward stock needs inspection
if qi_required: # validate row only if inspection is required on item level
if qi_required: # validate row only if inspection is required on item level
self.validate_qi_presence(row)
if self.docstatus == 1:
self.validate_qi_submission(row)
@@ -381,12 +475,16 @@ class StockController(AccountsController):
def validate_qi_submission(self, row):
"""Check if QI is submitted on row level, during submission"""
action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted")
action = frappe.db.get_single_value(
"Stock Settings", "action_if_quality_inspection_is_not_submitted"
)
qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
if not qa_docstatus == 1:
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
msg = (
f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
)
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
else:
@@ -398,7 +496,7 @@ class StockController(AccountsController):
qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status")
if qa_status == "Rejected":
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}"
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
@@ -411,48 +509,71 @@ class StockController(AccountsController):
frappe.get_doc("Blanket Order", blanket_order).update_ordered_qty()
def validate_customer_provided_item(self):
for d in self.get('items'):
for d in self.get("items"):
# Customer Provided parts will have zero valuation rate
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
if frappe.db.get_value("Item", d.item_code, "is_customer_provided_item"):
d.allow_zero_valuation_rate = 1
def set_rate_of_stock_uom(self):
if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
if self.doctype in [
"Purchase Receipt",
"Purchase Invoice",
"Purchase Order",
"Sales Invoice",
"Sales Order",
"Delivery Note",
"Quotation",
]:
for d in self.get("items"):
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
def validate_internal_transfer(self):
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
and self.is_internal_transfer():
if (
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
and self.is_internal_transfer()
):
self.validate_in_transit_warehouses()
self.validate_multi_currency()
self.validate_packed_items()
def validate_in_transit_warehouses(self):
if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
for item in self.get('items'):
if (
self.doctype == "Sales Invoice" and self.get("update_stock")
) or self.doctype == "Delivery Note":
for item in self.get("items"):
if not item.target_warehouse:
frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
frappe.throw(
_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx)
)
if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
for item in self.get('items'):
if (
self.doctype == "Purchase Invoice" and self.get("update_stock")
) or self.doctype == "Purchase Receipt":
for item in self.get("items"):
if not item.from_warehouse:
frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
frappe.throw(
_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx)
)
def validate_multi_currency(self):
if self.currency != self.company_currency:
frappe.throw(_("Internal transfers can only be done in company's default currency"))
def validate_packed_items(self):
if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
if self.doctype in ("Sales Invoice", "Delivery Note Item") and self.get("packed_items"):
frappe.throw(_("Packed Items cannot be transferred internally"))
def validate_putaway_capacity(self):
# if over receipt is attempted while 'apply putaway rule' is disabled
# and if rule was applied on the transaction, validate it.
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
"Stock Reconciliation")
valid_doctype = self.doctype in (
"Purchase Receipt",
"Stock Entry",
"Purchase Invoice",
"Stock Reconciliation",
)
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
valid_doctype = False
@@ -461,14 +582,15 @@ class StockController(AccountsController):
rule_map = defaultdict(dict)
for item in self.get("items"):
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
rule = frappe.db.get_value("Putaway Rule",
{
"item_code": item.get("item_code"),
"warehouse": item.get(warehouse_field)
},
["name", "disable"], as_dict=True)
rule = frappe.db.get_value(
"Putaway Rule",
{"item_code": item.get("item_code"), "warehouse": item.get(warehouse_field)},
["name", "disable"],
as_dict=True,
)
if rule:
if rule.get("disabled"): continue # dont validate for disabled rule
if rule.get("disabled"):
continue # dont validate for disabled rule
if self.doctype == "Stock Reconciliation":
stock_qty = flt(item.qty)
@@ -489,46 +611,55 @@ class StockController(AccountsController):
frappe.throw(msg=message, title=_("Over Receipt"))
def prepare_over_receipt_message(self, rule, values):
message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
.format(
frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
)
message = _(
"{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}."
).format(
frappe.bold(values["qty_put"]),
frappe.bold(values["item"]),
frappe.bold(values["warehouse"]),
frappe.bold(values["capacity"]),
)
message += "<br><br>"
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
return message
def repost_future_sle_and_gle(self):
args = frappe._dict({
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company
})
args = frappe._dict(
{
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company,
}
)
if future_sle_exists(args) or repost_required_for_queue(self):
item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"))
item_based_reposting = cint(
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
)
if item_based_reposting:
create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name)
else:
create_repost_item_valuation_entry(args)
def repost_required_for_queue(doc: StockController) -> bool:
"""check if stock document contains repeated item-warehouse with queue based valuation.
if queue exists for repeated items then SLEs need to reprocessed in background again.
"""
consuming_sles = frappe.db.get_all("Stock Ledger Entry",
consuming_sles = frappe.db.get_all(
"Stock Ledger Entry",
filters={
"voucher_type": doc.doctype,
"voucher_no": doc.name,
"actual_qty": ("<", 0),
"is_cancelled": 0
"is_cancelled": 0,
},
fields=["item_code", "warehouse", "stock_queue"]
fields=["item_code", "warehouse", "stock_queue"],
)
item_warehouses = [(sle.item_code, sle.warehouse) for sle in consuming_sles]
@@ -551,32 +682,41 @@ def make_quality_inspections(doctype, docname, items):
inspections = []
for item in items:
if flt(item.get("sample_size")) > flt(item.get("qty")):
frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format(
item_name=item.get("item_name"),
sample_size=item.get("sample_size"),
accepted_quantity=item.get("qty")
))
frappe.throw(
_(
"{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})"
).format(
item_name=item.get("item_name"),
sample_size=item.get("sample_size"),
accepted_quantity=item.get("qty"),
)
)
quality_inspection = frappe.get_doc({
"doctype": "Quality Inspection",
"inspection_type": "Incoming",
"inspected_by": frappe.session.user,
"reference_type": doctype,
"reference_name": docname,
"item_code": item.get("item_code"),
"description": item.get("description"),
"sample_size": flt(item.get("sample_size")),
"item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
"batch_no": item.get("batch_no")
}).insert()
quality_inspection = frappe.get_doc(
{
"doctype": "Quality Inspection",
"inspection_type": "Incoming",
"inspected_by": frappe.session.user,
"reference_type": doctype,
"reference_name": docname,
"item_code": item.get("item_code"),
"description": item.get("description"),
"sample_size": flt(item.get("sample_size")),
"item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
"batch_no": item.get("batch_no"),
}
).insert()
quality_inspection.save()
inspections.append(quality_inspection.name)
return inspections
def is_reposting_pending():
return frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
return frappe.db.exists(
"Repost Item Valuation", {"docstatus": 1, "status": ["in", ["Queued", "In Progress"]]}
)
def future_sle_exists(args, sl_entries=None):
key = (args.voucher_type, args.voucher_no)
@@ -593,7 +733,8 @@ def future_sle_exists(args, sl_entries=None):
or_conditions = get_conditions_to_validate_future_sle(sl_entries)
data = frappe.db.sql("""
data = frappe.db.sql(
"""
select item_code, warehouse, count(name) as total_row
from `tabStock Ledger Entry` force index (item_warehouse)
where
@@ -604,43 +745,55 @@ def future_sle_exists(args, sl_entries=None):
and is_cancelled = 0
GROUP BY
item_code, warehouse
""".format(" or ".join(or_conditions)), args, as_dict=1)
""".format(
" or ".join(or_conditions)
),
args,
as_dict=1,
)
for d in data:
frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
return len(data)
def validate_future_sle_not_exists(args, key, sl_entries=None):
item_key = ''
if args.get('item_code'):
item_key = (args.get('item_code'), args.get('warehouse'))
if not sl_entries and hasattr(frappe.local, 'future_sle'):
if (not frappe.local.future_sle.get(key) or
(item_key and item_key not in frappe.local.future_sle.get(key))):
def validate_future_sle_not_exists(args, key, sl_entries=None):
item_key = ""
if args.get("item_code"):
item_key = (args.get("item_code"), args.get("warehouse"))
if not sl_entries and hasattr(frappe.local, "future_sle"):
if not frappe.local.future_sle.get(key) or (
item_key and item_key not in frappe.local.future_sle.get(key)
):
return True
def get_cached_data(args, key):
if not hasattr(frappe.local, 'future_sle'):
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}
if key not in frappe.local.future_sle:
frappe.local.future_sle[key] = frappe._dict({})
if args.get('item_code'):
item_key = (args.get('item_code'), args.get('warehouse'))
if args.get("item_code"):
item_key = (args.get("item_code"), args.get("warehouse"))
count = frappe.local.future_sle[key].get(item_key)
return True if (count or count == 0) else False
else:
return frappe.local.future_sle[key]
def get_sle_entries_against_voucher(args):
return frappe.get_all("Stock Ledger Entry",
return frappe.get_all(
"Stock Ledger Entry",
filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
fields=["item_code", "warehouse"],
order_by="creation asc")
order_by="creation asc",
)
def get_conditions_to_validate_future_sle(sl_entries):
warehouse_items_map = {}
@@ -654,16 +807,18 @@ def get_conditions_to_validate_future_sle(sl_entries):
for warehouse, items in warehouse_items_map.items():
or_conditions.append(
f"""warehouse = {frappe.db.escape(warehouse)}
and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""")
and item_code in ({', '.join(frappe.db.escape(item) for item in items)})"""
)
return or_conditions
def create_repost_item_valuation_entry(args):
args = frappe._dict(args)
repost_entry = frappe.new_doc("Repost Item Valuation")
repost_entry.based_on = args.based_on
if not args.based_on:
repost_entry.based_on = 'Transaction' if args.voucher_no else "Item and Warehouse"
repost_entry.based_on = "Transaction" if args.voucher_no else "Item and Warehouse"
repost_entry.voucher_type = args.voucher_type
repost_entry.voucher_no = args.voucher_no
repost_entry.item_code = args.item_code

View File

@@ -8,9 +8,9 @@ from frappe.utils import cint, flt, get_link_to_form
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class Subcontracting():
class Subcontracting:
def set_materials_for_subcontracted_items(self, raw_material_table):
if self.doctype == 'Purchase Invoice' and not self.update_stock:
if self.doctype == "Purchase Invoice" and not self.update_stock:
return
self.raw_material_table = raw_material_table
@@ -33,13 +33,14 @@ class Subcontracting():
self.__get_backflush_based_on()
def __get_backflush_based_on(self):
self.backflush_based_on = frappe.db.get_single_value("Buying Settings",
"backflush_raw_materials_of_subcontract_based_on")
self.backflush_based_on = frappe.db.get_single_value(
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
)
def __get_purchase_orders(self):
self.purchase_orders = []
if self.doctype == 'Purchase Order':
if self.doctype == "Purchase Order":
return
self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order]
@@ -48,7 +49,7 @@ class Subcontracting():
self.__changed_name = []
self.__reference_name = []
if self.doctype == 'Purchase Order' or self.is_new():
if self.doctype == "Purchase Order" or self.is_new():
self.set(self.raw_material_table, [])
return
@@ -68,20 +69,20 @@ class Subcontracting():
def __get_data_before_save(self):
item_dict = {}
if self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self._doc_before_save:
for row in self._doc_before_save.get('items'):
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self._doc_before_save:
for row in self._doc_before_save.get("items"):
item_dict[row.name] = (row.item_code, row.qty)
return item_dict
def get_available_materials(self):
''' Get the available raw materials which has been transferred to the supplier.
available_materials = {
(item_code, subcontracted_item, purchase_order): {
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
}
}
'''
"""Get the available raw materials which has been transferred to the supplier.
available_materials = {
(item_code, subcontracted_item, purchase_order): {
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
}
}
"""
if not self.purchase_orders:
return
@@ -89,8 +90,17 @@ class Subcontracting():
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
if key not in self.available_materials:
self.available_materials.setdefault(key, frappe._dict({'qty': 0, 'serial_no': [],
'batch_no': defaultdict(float), 'item_details': row, 'po_details': []})
self.available_materials.setdefault(
key,
frappe._dict(
{
"qty": 0,
"serial_no": [],
"batch_no": defaultdict(float),
"item_details": row,
"po_details": [],
}
),
)
details = self.available_materials[key]
@@ -106,17 +116,17 @@ class Subcontracting():
self.__set_alternative_item_details(row)
self.__transferred_items = copy.deepcopy(self.available_materials)
for doctype in ['Purchase Receipt', 'Purchase Invoice']:
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
self.__update_consumed_materials(doctype)
def __update_consumed_materials(self, doctype, return_consumed_items=False):
'''Deduct the consumed materials from the available materials.'''
"""Deduct the consumed materials from the available materials."""
pr_items = self.__get_received_items(doctype)
if not pr_items:
return ([], {}) if return_consumed_items else None
pr_items = {d.name: d.get(self.get('po_field') or 'purchase_order') for d in pr_items}
pr_items = {d.name: d.get(self.get("po_field") or "purchase_order") for d in pr_items}
consumed_materials = self.__get_consumed_items(doctype, pr_items.keys())
if return_consumed_items:
@@ -127,97 +137,153 @@ class Subcontracting():
if not self.available_materials.get(key):
continue
self.available_materials[key]['qty'] -= row.consumed_qty
self.available_materials[key]["qty"] -= row.consumed_qty
if row.serial_no:
self.available_materials[key]['serial_no'] = list(
set(self.available_materials[key]['serial_no']) - set(get_serial_nos(row.serial_no))
self.available_materials[key]["serial_no"] = list(
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
)
if row.batch_no:
self.available_materials[key]['batch_no'][row.batch_no] -= row.consumed_qty
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
def __get_transferred_items(self):
fields = ['`tabStock Entry`.`purchase_order`']
alias_dict = {'item_code': 'rm_item_code', 'subcontracted_item': 'main_item_code', 'basic_rate': 'rate'}
fields = ["`tabStock Entry`.`purchase_order`"]
alias_dict = {
"item_code": "rm_item_code",
"subcontracted_item": "main_item_code",
"basic_rate": "rate",
}
child_table_fields = ['item_code', 'item_name', 'description', 'qty', 'basic_rate', 'amount',
'serial_no', 'uom', 'subcontracted_item', 'stock_uom', 'batch_no', 'conversion_factor',
's_warehouse', 't_warehouse', 'item_group', 'po_detail']
child_table_fields = [
"item_code",
"item_name",
"description",
"qty",
"basic_rate",
"amount",
"serial_no",
"uom",
"subcontracted_item",
"stock_uom",
"batch_no",
"conversion_factor",
"s_warehouse",
"t_warehouse",
"item_group",
"po_detail",
]
if self.backflush_based_on == 'BOM':
child_table_fields.append('original_item')
if self.backflush_based_on == "BOM":
child_table_fields.append("original_item")
for field in child_table_fields:
fields.append(f'`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}')
fields.append(f"`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}")
filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purpose', '=', 'Send to Subcontractor'],
['Stock Entry', 'purchase_order', 'in', self.purchase_orders]]
filters = [
["Stock Entry", "docstatus", "=", 1],
["Stock Entry", "purpose", "=", "Send to Subcontractor"],
["Stock Entry", "purchase_order", "in", self.purchase_orders],
]
return frappe.get_all('Stock Entry', fields = fields, filters=filters)
return frappe.get_all("Stock Entry", fields=fields, filters=filters)
def __get_received_items(self, doctype):
fields = []
self.po_field = 'purchase_order'
self.po_field = "purchase_order"
for field in ['name', self.po_field, 'parent']:
fields.append(f'`tab{doctype} Item`.`{field}`')
for field in ["name", self.po_field, "parent"]:
fields.append(f"`tab{doctype} Item`.`{field}`")
filters = [[doctype, 'docstatus', '=', 1], [f'{doctype} Item', self.po_field, 'in', self.purchase_orders]]
if doctype == 'Purchase Invoice':
filters.append(['Purchase Invoice', 'update_stock', "=", 1])
filters = [
[doctype, "docstatus", "=", 1],
[f"{doctype} Item", self.po_field, "in", self.purchase_orders],
]
if doctype == "Purchase Invoice":
filters.append(["Purchase Invoice", "update_stock", "=", 1])
return frappe.get_all(f'{doctype}', fields = fields, filters = filters)
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
def __get_consumed_items(self, doctype, pr_items):
return frappe.get_all('Purchase Receipt Item Supplied',
fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'],
filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items)), 'parenttype': doctype})
return frappe.get_all(
"Purchase Receipt Item Supplied",
fields=[
"serial_no",
"rm_item_code",
"reference_name",
"batch_no",
"consumed_qty",
"main_item_code",
],
filters={"docstatus": 1, "reference_name": ("in", list(pr_items)), "parenttype": doctype},
)
def __set_alternative_item_details(self, row):
if row.get('original_item'):
self.alternative_item_details[row.get('original_item')] = row
if row.get("original_item"):
self.alternative_item_details[row.get("original_item")] = row
def __get_pending_qty_to_receive(self):
'''Get qty to be received against the purchase order.'''
"""Get qty to be received against the purchase order."""
self.qty_to_be_received = defaultdict(float)
if self.doctype != 'Purchase Order' and self.backflush_based_on != 'BOM' and self.purchase_orders:
for row in frappe.get_all('Purchase Order Item',
fields = ['item_code', '(qty - received_qty) as qty', 'parent', 'name'],
filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}):
if (
self.doctype != "Purchase Order" and self.backflush_based_on != "BOM" and self.purchase_orders
):
for row in frappe.get_all(
"Purchase Order Item",
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
filters={"docstatus": 1, "parent": ("in", self.purchase_orders)},
):
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
doctype = 'BOM Item' if not exploded_item else 'BOM Explosion Item'
fields = [f'`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit']
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
alias_dict = {'item_code': 'rm_item_code', 'name': 'bom_detail_no', 'source_warehouse': 'reserve_warehouse'}
for field in ['item_code', 'name', 'rate', 'stock_uom',
'source_warehouse', 'description', 'item_name', 'stock_uom']:
fields.append(f'`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}')
alias_dict = {
"item_code": "rm_item_code",
"name": "bom_detail_no",
"source_warehouse": "reserve_warehouse",
}
for field in [
"item_code",
"name",
"rate",
"stock_uom",
"source_warehouse",
"description",
"item_name",
"stock_uom",
]:
fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}")
filters = [[doctype, 'parent', '=', bom_no], [doctype, 'docstatus', '=', 1],
['BOM', 'item', '=', item_code], [doctype, 'sourced_by_supplier', '=', 0]]
filters = [
[doctype, "parent", "=", bom_no],
[doctype, "docstatus", "=", 1],
["BOM", "item", "=", item_code],
[doctype, "sourced_by_supplier", "=", 0],
]
return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or []
return (
frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
)
def __remove_changed_rows(self):
if not self.__changed_name:
return
i=1
i = 1
self.set(self.raw_material_table, [])
for d in self._doc_before_save.supplied_items:
if d.reference_name in self.__changed_name:
continue
if (d.reference_name not in self.__reference_name):
if d.reference_name not in self.__reference_name:
continue
d.idx = i
self.append('supplied_items', d)
self.append("supplied_items", d)
i += 1
@@ -226,31 +292,35 @@ class Subcontracting():
has_supplied_items = True if self.get(self.raw_material_table) else False
for row in self.items:
if (self.doctype != 'Purchase Order' and ((self.__changed_name and row.name not in self.__changed_name)
or (has_supplied_items and not self.__changed_name))):
if self.doctype != "Purchase Order" and (
(self.__changed_name and row.name not in self.__changed_name)
or (has_supplied_items and not self.__changed_name)
):
continue
if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM':
for bom_item in self.__get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')):
qty = (flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor)
if self.doctype == "Purchase Order" or self.backflush_based_on == "BOM":
for bom_item in self.__get_materials_from_bom(
row.item_code, row.bom, row.get("include_exploded_items")
):
qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor
bom_item.main_item_code = row.item_code
self.__update_reserve_warehouse(bom_item, row)
self.__set_alternative_item(bom_item)
self.__add_supplied_item(row, bom_item, qty)
elif self.backflush_based_on != 'BOM':
elif self.backflush_based_on != "BOM":
for key, transfer_item in self.available_materials.items():
if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0:
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
transfer_item.qty -= qty
self.__add_supplied_item(row, transfer_item.get('item_details'), qty)
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
if self.qty_to_be_received:
self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty
def __update_reserve_warehouse(self, row, item):
if self.doctype == 'Purchase Order':
row.reserve_warehouse = (self.set_reserve_warehouse or item.warehouse)
if self.doctype == "Purchase Order":
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
key = (item_row.item_code, item_row.purchase_order)
@@ -262,8 +332,9 @@ class Subcontracting():
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
transfer_item.item_details.required_qty = transfer_item.qty
if (transfer_item.serial_no or frappe.get_cached_value('UOM',
transfer_item.item_details.stock_uom, 'must_be_whole_number')):
if transfer_item.serial_no or frappe.get_cached_value(
"UOM", transfer_item.item_details.stock_uom, "must_be_whole_number"
):
return frappe.utils.ceil(qty)
return qty
@@ -277,7 +348,7 @@ class Subcontracting():
rm_obj = self.append(self.raw_material_table, bom_item)
rm_obj.reference_name = item_row.name
if self.doctype == 'Purchase Order':
if self.doctype == "Purchase Order":
rm_obj.required_qty = qty
else:
rm_obj.consumed_qty = 0
@@ -287,12 +358,12 @@ class Subcontracting():
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
if (self.available_materials.get(key) and self.available_materials[key]['batch_no']):
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
new_rm_obj = None
for batch_no, batch_qty in self.available_materials[key]['batch_no'].items():
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
if batch_qty >= qty:
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
self.available_materials[key]['batch_no'][batch_no] -= qty
self.available_materials[key]["batch_no"][batch_no] -= qty
return
elif qty > 0 and batch_qty > 0:
@@ -300,7 +371,7 @@ class Subcontracting():
new_rm_obj = self.append(self.raw_material_table, bom_item)
new_rm_obj.reference_name = item_row.name
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
self.available_materials[key]['batch_no'][batch_no] = 0
self.available_materials[key]["batch_no"][batch_no] = 0
if abs(qty) > 0 and not new_rm_obj:
self.__set_consumed_qty(rm_obj, qty)
@@ -313,29 +384,35 @@ class Subcontracting():
rm_obj.consumed_qty = consumed_qty
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no,
'required_qty': qty, 'purchase_order': item_row.purchase_order})
rm_obj.update(
{
"consumed_qty": qty,
"batch_no": batch_no,
"required_qty": qty,
"purchase_order": item_row.purchase_order,
}
)
self.__set_serial_nos(item_row, rm_obj)
def __set_serial_nos(self, item_row, rm_obj):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
if (self.available_materials.get(key) and self.available_materials[key]['serial_no']):
used_serial_nos = self.available_materials[key]['serial_no'][0: cint(rm_obj.consumed_qty)]
rm_obj.serial_no = '\n'.join(used_serial_nos)
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
rm_obj.serial_no = "\n".join(used_serial_nos)
# Removed the used serial nos from the list
for sn in used_serial_nos:
self.available_materials[key]['serial_no'].remove(sn)
self.available_materials[key]["serial_no"].remove(sn)
def set_consumed_qty_in_po(self):
# Update consumed qty back in the purchase order
if self.is_subcontracted != 'Yes':
if self.is_subcontracted != "Yes":
return
self.__get_purchase_orders()
itemwise_consumed_qty = defaultdict(float)
for doctype in ['Purchase Receipt', 'Purchase Invoice']:
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True)
for row in consumed_items:
@@ -345,10 +422,12 @@ class Subcontracting():
self.__update_consumed_qty_in_po(itemwise_consumed_qty)
def __update_consumed_qty_in_po(self, itemwise_consumed_qty):
fields = ['main_item_code', 'rm_item_code', 'parent', 'supplied_qty', 'name']
filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}
fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
filters = {"docstatus": 1, "parent": ("in", self.purchase_orders)}
for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters, order_by='idx'):
for row in frappe.get_all(
"Purchase Order Item Supplied", fields=fields, filters=filters, order_by="idx"
):
key = (row.rm_item_code, row.main_item_code, row.parent)
consumed_qty = itemwise_consumed_qty.get(key, 0)
@@ -356,10 +435,10 @@ class Subcontracting():
consumed_qty = row.supplied_qty
itemwise_consumed_qty[key] -= consumed_qty
frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty)
frappe.db.set_value("Purchase Order Item Supplied", row.name, "consumed_qty", consumed_qty)
def __validate_supplied_items(self):
if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
if self.doctype not in ["Purchase Invoice", "Purchase Receipt"]:
return
for row in self.get(self.raw_material_table):
@@ -371,18 +450,20 @@ class Subcontracting():
self.__validate_serial_no(row, key)
def __validate_batch_no(self, row, key):
if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
link = get_link_to_form('Purchase Order', row.purchase_order)
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
"batch_no"
):
link = get_link_to_form("Purchase Order", row.purchase_order)
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
def __validate_serial_no(self, row, key):
if row.get('serial_no'):
serial_nos = get_serial_nos(row.get('serial_no'))
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no'))
if row.get("serial_no"):
serial_nos = get_serial_nos(row.get("serial_no"))
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get("serial_no"))
if incorrect_sn:
incorrect_sn = "\n".join(incorrect_sn)
link = get_link_to_form('Purchase Order', row.purchase_order)
msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
link = get_link_to_form("Purchase Order", row.purchase_order)
msg = f"The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}"
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))

View File

@@ -59,23 +59,23 @@ class calculate_taxes_and_totals(object):
self.calculate_total_net_weight()
def validate_item_tax_template(self):
for item in self.doc.get('items'):
if item.item_code and item.get('item_tax_template'):
for item in self.doc.get("items"):
if item.item_code and item.get("item_tax_template"):
item_doc = frappe.get_cached_doc("Item", item.item_code)
args = {
'net_rate': item.net_rate or item.rate,
'tax_category': self.doc.get('tax_category'),
'posting_date': self.doc.get('posting_date'),
'bill_date': self.doc.get('bill_date'),
'transaction_date': self.doc.get('transaction_date'),
'company': self.doc.get('company')
"net_rate": item.net_rate or item.rate,
"tax_category": self.doc.get("tax_category"),
"posting_date": self.doc.get("posting_date"),
"bill_date": self.doc.get("bill_date"),
"transaction_date": self.doc.get("transaction_date"),
"company": self.doc.get("company"),
}
item_group = item_doc.item_group
item_group_taxes = []
while item_group:
item_group_doc = frappe.get_cached_doc('Item Group', item_group)
item_group_doc = frappe.get_cached_doc("Item Group", item_group)
item_group_taxes += item_group_doc.taxes or []
item_group = item_group_doc.parent_item_group
@@ -90,9 +90,11 @@ class calculate_taxes_and_totals(object):
if taxes:
if item.item_tax_template not in taxes:
item.item_tax_template = taxes[0]
frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format(
item.idx, frappe.bold(item.item_code)
))
frappe.msgprint(
_("Row {0}: Item Tax template updated as per validity and rate applied").format(
item.idx, frappe.bold(item.item_code)
)
)
def validate_conversion_rate(self):
# validate conversion rate
@@ -101,13 +103,17 @@ class calculate_taxes_and_totals(object):
self.doc.currency = company_currency
self.doc.conversion_rate = 1.0
else:
validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
self.doc.meta.get_label("conversion_rate"), self.doc.company)
validate_conversion_rate(
self.doc.currency,
self.doc.conversion_rate,
self.doc.meta.get_label("conversion_rate"),
self.doc.company,
)
self.doc.conversion_rate = flt(self.doc.conversion_rate)
def calculate_item_values(self):
if self.doc.get('is_consolidated'):
if self.doc.get("is_consolidated"):
return
if not self.discount_amount_applied:
@@ -118,19 +124,30 @@ class calculate_taxes_and_totals(object):
item.rate = 0.0
elif item.price_list_rate:
if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
item.rate = flt(item.price_list_rate *
(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
item.rate = flt(
item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
)
item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
elif item.discount_amount and item.pricing_rules:
item.rate = item.price_list_rate - item.discount_amount
item.rate = item.price_list_rate - item.discount_amount
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
if item.doctype in [
"Quotation Item",
"Sales Order Item",
"Delivery Note Item",
"Sales Invoice Item",
"POS Invoice Item",
"Purchase Invoice Item",
"Purchase Order Item",
"Purchase Receipt 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"))
item.rate = flt(
item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
)
if item.discount_amount and not item.discount_percentage:
item.rate = item.rate_with_margin - item.discount_amount
@@ -149,18 +166,22 @@ class calculate_taxes_and_totals(object):
elif not item.qty and self.doc.get("is_debit_note"):
item.amount = flt(item.rate, item.precision("amount"))
else:
item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.net_amount = item.amount
self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
self._set_in_company_currency(
item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"]
)
item.item_tax_amount = 0.0
def _set_in_company_currency(self, doc, fields):
"""set values in base currency"""
for f in fields:
val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
val = flt(
flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f)
)
doc.set("base_" + f, val)
def initialize_taxes(self):
@@ -169,16 +190,22 @@ class calculate_taxes_and_totals(object):
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, self.doc)
if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
tax.item_wise_tax_detail = {}
tax_fields = ["total", "tax_amount_after_discount_amount",
"tax_amount_for_current_item", "grand_total_for_current_item",
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
tax_fields = [
"total",
"tax_amount_after_discount_amount",
"tax_amount_for_current_item",
"grand_total_for_current_item",
"tax_fraction_for_current_item",
"grand_total_fraction_for_current_item",
]
if tax.charge_type != "Actual" and \
not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
tax_fields.append("tax_amount")
if tax.charge_type != "Actual" and not (
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
):
tax_fields.append("tax_amount")
for fieldname in tax_fields:
tax.set(fieldname, 0.0)
@@ -194,25 +221,32 @@ class calculate_taxes_and_totals(object):
cumulated_tax_fraction = 0
total_inclusive_tax_amount_per_qty = 0
for i, tax in enumerate(self.doc.get("taxes")):
tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
(
tax.tax_fraction_for_current_item,
inclusive_tax_amount_per_qty,
) = self.get_current_tax_fraction(tax, item_tax_map)
if i==0:
if i == 0:
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
else:
tax.grand_total_fraction_for_current_item = \
self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
tax.grand_total_fraction_for_current_item = (
self.doc.get("taxes")[i - 1].grand_total_fraction_for_current_item
+ tax.tax_fraction_for_current_item
)
cumulated_tax_fraction += tax.tax_fraction_for_current_item
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
if (
not self.discount_amount_applied
and item.qty
and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty)
):
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
item.discount_percentage = flt(item.discount_percentage,
item.precision("discount_percentage"))
item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
self._set_in_company_currency(item, ["net_rate", "net_amount"])
@@ -221,8 +255,8 @@ class calculate_taxes_and_totals(object):
def get_current_tax_fraction(self, tax, item_tax_map):
"""
Get tax fraction for calculating tax exclusive amount
from tax inclusive amount
Get tax fraction for calculating tax exclusive amount
from tax inclusive amount
"""
current_tax_fraction = 0
inclusive_tax_amount_per_qty = 0
@@ -234,12 +268,14 @@ class calculate_taxes_and_totals(object):
current_tax_fraction = tax_rate / 100.0
elif tax.charge_type == "On Previous Row Amount":
current_tax_fraction = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
cint(tax.row_id) - 1
].tax_fraction_for_current_item
elif tax.charge_type == "On Previous Row Total":
current_tax_fraction = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
cint(tax.row_id) - 1
].grand_total_fraction_for_current_item
elif tax.charge_type == "On Item Quantity":
inclusive_tax_amount_per_qty = flt(tax_rate)
@@ -257,7 +293,9 @@ class calculate_taxes_and_totals(object):
return tax.rate
def calculate_net_total(self):
self.doc.total_qty = self.doc.total = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
self.doc.total_qty = (
self.doc.total
) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
for item in self.doc.get("items"):
self.doc.total += item.amount
@@ -276,13 +314,20 @@ class calculate_taxes_and_totals(object):
self._calculate()
def calculate_taxes(self):
rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get(
"rounding_adjustment"
)
if not rounding_adjustment_computed:
self.doc.rounding_adjustment = 0
# maintain actual tax rate based on idx
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
actual_tax_dict = dict(
[
[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
for tax in self.doc.get("taxes")
if tax.charge_type == "Actual"
]
)
for n, item in enumerate(self.doc.get("items")):
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
@@ -297,9 +342,10 @@ class calculate_taxes_and_totals(object):
current_tax_amount += actual_tax_dict[tax.idx]
# accumulate tax amount into tax.tax_amount
if tax.charge_type != "Actual" and \
not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
tax.tax_amount += current_tax_amount
if tax.charge_type != "Actual" and not (
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
):
tax.tax_amount += current_tax_amount
# store tax_amount for current item as it will be used for
# charge type = 'On Previous Row Amount'
@@ -312,17 +358,17 @@ class calculate_taxes_and_totals(object):
# note: grand_total_for_current_item contains the contribution of
# item's amount, previously applied tax and the current tax on that item
if i==0:
if i == 0:
tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
else:
tax.grand_total_for_current_item = \
flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
tax.grand_total_for_current_item = flt(
self.doc.get("taxes")[i - 1].grand_total_for_current_item + current_tax_amount
)
# set precision in the last item iteration
if n == len(self.doc.get("items")) - 1:
self.round_off_totals(tax)
self._set_in_company_currency(tax,
["tax_amount", "tax_amount_after_discount_amount"])
self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
self.round_off_base_values(tax)
self.set_cumulative_total(i, tax)
@@ -330,20 +376,29 @@ class calculate_taxes_and_totals(object):
self._set_in_company_currency(tax, ["total"])
# adjust Discount Amount loss in last tax iteration
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
and self.doc.discount_amount \
and self.doc.apply_discount_on == "Grand Total" \
and not rounding_adjustment_computed:
self.doc.rounding_adjustment = flt(self.doc.grand_total
- flt(self.doc.discount_amount) - tax.total,
self.doc.precision("rounding_adjustment"))
if (
i == (len(self.doc.get("taxes")) - 1)
and self.discount_amount_applied
and self.doc.discount_amount
and self.doc.apply_discount_on == "Grand Total"
and not rounding_adjustment_computed
):
self.doc.rounding_adjustment = flt(
self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
self.doc.precision("rounding_adjustment"),
)
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
# if just for valuation, do not add the tax amount in total
# if tax/charges is for deduction, multiply by -1
if getattr(tax, "category", None):
tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
if self.doc.doctype in [
"Purchase Order",
"Purchase Invoice",
"Purchase Receipt",
"Supplier Quotation",
]:
tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
return tax_amount
@@ -354,7 +409,7 @@ class calculate_taxes_and_totals(object):
if row_idx == 0:
tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
else:
tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
tax.total = flt(self.doc.get("taxes")[row_idx - 1].total + tax_amount, tax.precision("total"))
def get_current_tax_amount(self, item, tax, item_tax_map):
tax_rate = self._get_tax_rate(tax, item_tax_map)
@@ -363,16 +418,20 @@ class calculate_taxes_and_totals(object):
if tax.charge_type == "Actual":
# distribute the tax amount proportionally to each item row
actual = flt(tax.tax_amount, tax.precision("tax_amount"))
current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
current_tax_amount = (
item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
)
elif tax.charge_type == "On Net Total":
current_tax_amount = (tax_rate / 100.0) * item.net_amount
elif tax.charge_type == "On Previous Row Amount":
current_tax_amount = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
cint(tax.row_id) - 1
].tax_amount_for_current_item
elif tax.charge_type == "On Previous Row Total":
current_tax_amount = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
cint(tax.row_id) - 1
].grand_total_for_current_item
elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.qty
@@ -384,11 +443,11 @@ class calculate_taxes_and_totals(object):
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
# store tax breakup for each item
key = item.item_code or item.item_name
item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate
if tax.item_wise_tax_detail.get(key):
item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
def round_off_totals(self, tax):
if tax.account_head in frappe.flags.round_off_applicable_accounts:
@@ -396,8 +455,9 @@ class calculate_taxes_and_totals(object):
tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
tax.precision("tax_amount"))
tax.tax_amount_after_discount_amount = flt(
tax.tax_amount_after_discount_amount, tax.precision("tax_amount")
)
def round_off_base_values(self, tax):
# Round off to nearest integer based on regional settings
@@ -409,11 +469,15 @@ class calculate_taxes_and_totals(object):
# if fully inclusive taxes and diff
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
last_tax = self.doc.get("taxes")[-1]
non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
for d in self.doc.get("taxes") if not d.included_in_print_rate)
non_inclusive_tax_amount = sum(
flt(d.tax_amount_after_discount_amount)
for d in self.doc.get("taxes")
if not d.included_in_print_rate
)
diff = self.doc.total + non_inclusive_tax_amount \
- flt(last_tax.total, last_tax.precision("total"))
diff = (
self.doc.total + non_inclusive_tax_amount - flt(last_tax.total, last_tax.precision("total"))
)
# If discount amount applied, deduct the discount amount
# because self.doc.total is always without discount, but last_tax.total is after discount
@@ -422,7 +486,7 @@ class calculate_taxes_and_totals(object):
diff = flt(diff, self.doc.precision("rounding_adjustment"))
if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
self.doc.rounding_adjustment = diff
def calculate_totals(self):
@@ -432,16 +496,27 @@ class calculate_taxes_and_totals(object):
self.doc.grand_total = flt(self.doc.net_total)
if self.doc.get("taxes"):
self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
- flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
self.doc.total_taxes_and_charges = flt(
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
self.doc.precision("total_taxes_and_charges"),
)
else:
self.doc.total_taxes_and_charges = 0.0
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
if self.doc.total_taxes_and_charges else self.doc.base_net_total
if self.doc.doctype in [
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"POS Invoice",
]:
self.doc.base_grand_total = (
flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total"))
if self.doc.total_taxes_and_charges
else self.doc.base_net_total
)
else:
self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
for tax in self.doc.get("taxes"):
@@ -453,26 +528,29 @@ class calculate_taxes_and_totals(object):
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
self.doc.base_grand_total = (
flt(self.doc.grand_total * self.doc.conversion_rate)
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted)
else self.doc.base_net_total
)
self._set_in_company_currency(self.doc,
["taxes_and_charges_added", "taxes_and_charges_deducted"])
self._set_in_company_currency(
self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]
)
self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
self.set_rounded_total()
def calculate_total_net_weight(self):
if self.doc.meta.get_field('total_net_weight'):
if self.doc.meta.get_field("total_net_weight"):
self.doc.total_net_weight = 0.0
for d in self.doc.items:
if d.total_weight:
self.doc.total_net_weight += d.total_weight
def set_rounded_total(self):
if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'):
if self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment"):
return
if self.doc.meta.get_field("rounded_total"):
@@ -480,33 +558,40 @@ class calculate_taxes_and_totals(object):
self.doc.rounded_total = self.doc.base_rounded_total = 0
return
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
self.doc.currency, self.doc.precision("rounded_total"))
self.doc.rounded_total = round_based_on_smallest_currency_fraction(
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
)
#if print_in_rate is set, we would have already calculated rounding adjustment
self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
self.doc.precision("rounding_adjustment"))
# if print_in_rate is set, we would have already calculated rounding adjustment
self.doc.rounding_adjustment += flt(
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
)
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
def _cleanup(self):
if not self.doc.get('is_consolidated'):
if not self.doc.get("is_consolidated"):
for tax in self.doc.get("taxes"):
if not tax.get("dont_recompute_tax"):
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(",", ":"))
def set_discount_amount(self):
if self.doc.additional_discount_percentage:
self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
* self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
self.doc.discount_amount = flt(
flt(self.doc.get(scrub(self.doc.apply_discount_on)))
* self.doc.additional_discount_percentage
/ 100,
self.doc.precision("discount_amount"),
)
def apply_discount_amount(self):
if self.doc.discount_amount:
if not self.doc.apply_discount_on:
frappe.throw(_("Please select Apply Discount On"))
self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
self.doc.precision("base_discount_amount"))
self.doc.base_discount_amount = flt(
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
)
total_for_discount_amount = self.get_total_for_discount_amount()
taxes = self.doc.get("taxes")
@@ -515,20 +600,24 @@ class calculate_taxes_and_totals(object):
if total_for_discount_amount:
# calculate item amount after Discount Amount
for i, item in enumerate(self.doc.get("items")):
distributed_amount = flt(self.doc.discount_amount) * \
item.net_amount / total_for_discount_amount
distributed_amount = (
flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
)
item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
net_total += item.net_amount
# discount amount rounding loss adjustment if no taxes
if (self.doc.apply_discount_on == "Net Total" or not taxes or total_for_discount_amount==self.doc.net_total) \
and i == len(self.doc.get("items")) - 1:
discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
self.doc.precision("net_total"))
if (
self.doc.apply_discount_on == "Net Total"
or not taxes
or total_for_discount_amount == self.doc.net_total
) and i == len(self.doc.get("items")) - 1:
discount_amount_loss = flt(
self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
)
item.net_amount = flt(item.net_amount + discount_amount_loss,
item.precision("net_amount"))
item.net_amount = flt(item.net_amount + discount_amount_loss, item.precision("net_amount"))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) if item.qty else 0
@@ -553,34 +642,44 @@ class calculate_taxes_and_totals(object):
actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
self.doc.precision("grand_total"))
return flt(
self.doc.grand_total - sum(actual_taxes_dict.values()), self.doc.precision("grand_total")
)
def calculate_total_advance(self):
if self.doc.docstatus < 2:
total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
for adv in self.doc.get("advances"))
total_allocated_amount = sum(
flt(adv.allocated_amount, adv.precision("allocated_amount"))
for adv in self.doc.get("advances")
)
self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
grand_total = self.doc.rounded_total or self.doc.grand_total
if self.doc.party_account_currency == self.doc.currency:
invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
self.doc.precision("grand_total"))
invoice_total = flt(
grand_total - flt(self.doc.write_off_amount), self.doc.precision("grand_total")
)
else:
base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
self.doc.precision("base_write_off_amount"))
invoice_total = flt(grand_total * self.doc.conversion_rate,
self.doc.precision("grand_total")) - base_write_off_amount
base_write_off_amount = flt(
flt(self.doc.write_off_amount) * self.doc.conversion_rate,
self.doc.precision("base_write_off_amount"),
)
invoice_total = (
flt(grand_total * self.doc.conversion_rate, self.doc.precision("grand_total"))
- base_write_off_amount
)
if invoice_total > 0 and self.doc.total_advance > invoice_total:
frappe.throw(_("Advance amount cannot be greater than {0} {1}")
.format(self.doc.party_account_currency, invoice_total))
frappe.throw(
_("Advance amount cannot be greater than {0} {1}").format(
self.doc.party_account_currency, invoice_total
)
)
if self.doc.docstatus == 0:
if self.doc.get('write_off_outstanding_amount_automatically'):
if self.doc.get("write_off_outstanding_amount_automatically"):
self.doc.write_off_amount = 0
self.calculate_outstanding_amount()
@@ -588,11 +687,11 @@ class calculate_taxes_and_totals(object):
def is_internal_invoice(self):
"""
Checks if its an internal transfer invoice
and decides if to calculate any out standing amount or not
Checks if its an internal transfer invoice
and decides if to calculate any out standing amount or not
"""
if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
if self.doc.doctype in ("Sales Invoice", "Purchase Invoice") and self.doc.is_internal_transfer():
return True
return False
@@ -604,42 +703,61 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount()
if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
self.is_internal_invoice(): return
if (
self.doc.is_return
and self.doc.return_against
and not self.doc.get("is_pos")
or self.is_internal_invoice()
):
return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
self._set_in_company_currency(self.doc, ['write_off_amount'])
self._set_in_company_currency(self.doc, ["write_off_amount"])
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
grand_total = self.doc.rounded_total or self.doc.grand_total
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
if self.doc.party_account_currency == self.doc.currency:
total_amount_to_pay = flt(grand_total - self.doc.total_advance
- flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
total_amount_to_pay = flt(
grand_total - self.doc.total_advance - flt(self.doc.write_off_amount),
self.doc.precision("grand_total"),
)
else:
total_amount_to_pay = flt(flt(base_grand_total, self.doc.precision("base_grand_total")) - self.doc.total_advance
- flt(self.doc.base_write_off_amount), self.doc.precision("base_grand_total"))
total_amount_to_pay = flt(
flt(base_grand_total, self.doc.precision("base_grand_total"))
- self.doc.total_advance
- flt(self.doc.base_write_off_amount),
self.doc.precision("base_grand_total"),
)
self.doc.round_floats_in(self.doc, ["paid_amount"])
change_amount = 0
if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
if self.doc.doctype == "Sales Invoice" and not self.doc.get("is_return"):
self.calculate_change_amount()
change_amount = self.doc.change_amount \
if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
change_amount = (
self.doc.change_amount
if self.doc.party_account_currency == self.doc.currency
else self.doc.base_change_amount
)
paid_amount = self.doc.paid_amount \
if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
paid_amount = (
self.doc.paid_amount
if self.doc.party_account_currency == self.doc.currency
else self.doc.base_paid_amount
)
self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
self.doc.precision("outstanding_amount"))
self.doc.outstanding_amount = flt(
total_amount_to_pay - flt(paid_amount) + flt(change_amount),
self.doc.precision("outstanding_amount"),
)
if (
self.doc.doctype == 'Sales Invoice'
and self.doc.get('is_pos')
and self.doc.get('is_return')
and not self.doc.get('is_consolidated')
self.doc.doctype == "Sales Invoice"
and self.doc.get("is_pos")
and self.doc.get("is_return")
and not self.doc.get("is_consolidated")
):
self.set_total_amount_to_default_mop(total_amount_to_pay)
self.calculate_paid_amount()
@@ -649,17 +767,17 @@ class calculate_taxes_and_totals(object):
paid_amount = base_paid_amount = 0.0
if self.doc.is_pos:
for payment in self.doc.get('payments'):
for payment in self.doc.get("payments"):
payment.amount = flt(payment.amount)
payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
paid_amount += payment.amount
base_paid_amount += payment.base_amount
elif not self.doc.is_return:
self.doc.set('payments', [])
self.doc.set("payments", [])
if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
base_paid_amount += self.doc.loyalty_amount
paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
paid_amount += self.doc.loyalty_amount / flt(self.doc.conversion_rate)
self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
@@ -670,21 +788,30 @@ class calculate_taxes_and_totals(object):
grand_total = self.doc.rounded_total or self.doc.grand_total
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
if self.doc.doctype == "Sales Invoice" \
and self.doc.paid_amount > grand_total and not self.doc.is_return \
and any(d.type == "Cash" for d in self.doc.payments):
if (
self.doc.doctype == "Sales Invoice"
and self.doc.paid_amount > grand_total
and not self.doc.is_return
and any(d.type == "Cash" for d in self.doc.payments)
):
self.doc.change_amount = flt(self.doc.paid_amount - grand_total,
self.doc.precision("change_amount"))
self.doc.change_amount = flt(
self.doc.paid_amount - grand_total, self.doc.precision("change_amount")
)
self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total,
self.doc.precision("base_change_amount"))
self.doc.base_change_amount = flt(
self.doc.base_paid_amount - base_grand_total, self.doc.precision("base_change_amount")
)
def calculate_write_off_amount(self):
if self.doc.get('write_off_outstanding_amount_automatically'):
self.doc.write_off_amount = flt(self.doc.outstanding_amount, self.doc.precision("write_off_amount"))
self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
self.doc.precision("base_write_off_amount"))
if self.doc.get("write_off_outstanding_amount_automatically"):
self.doc.write_off_amount = flt(
self.doc.outstanding_amount, self.doc.precision("write_off_amount")
)
self.doc.base_write_off_amount = flt(
self.doc.write_off_amount * self.doc.conversion_rate,
self.doc.precision("base_write_off_amount"),
)
self.calculate_outstanding_amount()
@@ -695,10 +822,15 @@ class calculate_taxes_and_totals(object):
if item.pricing_rules and not self.doc.ignore_pricing_rule:
has_margin = False
for d in get_applied_pricing_rules(item.pricing_rules):
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
pricing_rule = frappe.get_cached_doc("Pricing Rule", d)
if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
if pricing_rule.margin_rate_or_amount and (
(
pricing_rule.currency == self.doc.currency
and pricing_rule.margin_type in ["Amount", "Percentage"]
)
or pricing_rule.margin_type == "Percentage"
):
item.margin_type = pricing_rule.margin_type
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
has_margin = True
@@ -709,12 +841,17 @@ class calculate_taxes_and_totals(object):
if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
item.margin_type = "Amount"
item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
item.precision("margin_rate_or_amount"))
item.margin_rate_or_amount = flt(
item.rate - item.price_list_rate, item.precision("margin_rate_or_amount")
)
item.rate_with_margin = item.rate
elif item.margin_type and item.margin_rate_or_amount:
margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
margin_value = (
item.margin_rate_or_amount
if item.margin_type == "Amount"
else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
)
rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
@@ -724,16 +861,24 @@ class calculate_taxes_and_totals(object):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def set_total_amount_to_default_mop(self, total_amount_to_pay):
default_mode_of_payment = frappe.db.get_value('POS Payment Method',
{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
default_mode_of_payment = frappe.db.get_value(
"POS Payment Method",
{"parent": self.doc.pos_profile, "default": 1},
["mode_of_payment"],
as_dict=1,
)
if default_mode_of_payment:
self.doc.payments = []
self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment,
'amount': total_amount_to_pay,
'default': 1
})
self.doc.append(
"payments",
{
"mode_of_payment": default_mode_of_payment.mode_of_payment,
"amount": total_amount_to_pay,
"default": 1,
},
)
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
@@ -743,7 +888,7 @@ def get_itemised_tax_breakup_html(doc):
# get headers
tax_accounts = []
for tax in doc.taxes:
if getattr(tax, "category", None) and tax.category=="Valuation":
if getattr(tax, "category", None) and tax.category == "Valuation":
continue
if tax.description not in tax_accounts:
tax_accounts.append(tax.description)
@@ -759,34 +904,40 @@ def get_itemised_tax_breakup_html(doc):
frappe.flags.company = None
return frappe.render_template(
"templates/includes/itemised_tax_breakup.html", dict(
"templates/includes/itemised_tax_breakup.html",
dict(
headers=headers,
itemised_tax=itemised_tax,
itemised_taxable_amount=itemised_taxable_amount,
tax_accounts=tax_accounts,
doc=doc
)
doc=doc,
),
)
@frappe.whitelist()
def get_round_off_applicable_accounts(company, account_list):
account_list = get_regional_round_off_accounts(company, account_list)
return account_list
@erpnext.allow_regional
def get_regional_round_off_accounts(company, account_list):
pass
@erpnext.allow_regional
def update_itemised_tax_data(doc):
#Don't delete this method, used for localization
# Don't delete this method, used for localization
pass
@erpnext.allow_regional
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
return [_("Item"), _("Taxable Amount")] + tax_accounts
@erpnext.allow_regional
def get_itemised_tax_breakup_data(doc):
itemised_tax = get_itemised_tax(doc.taxes)
@@ -795,10 +946,11 @@ def get_itemised_tax_breakup_data(doc):
return itemised_tax, itemised_taxable_amount
def get_itemised_tax(taxes, with_tax_account=False):
itemised_tax = {}
for tax in taxes:
if getattr(tax, "category", None) and tax.category=="Valuation":
if getattr(tax, "category", None) and tax.category == "Valuation":
continue
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
@@ -815,16 +967,16 @@ def get_itemised_tax(taxes, with_tax_account=False):
else:
tax_rate = flt(tax_data)
itemised_tax[item_code][tax.description] = frappe._dict(dict(
tax_rate = tax_rate,
tax_amount = tax_amount
))
itemised_tax[item_code][tax.description] = frappe._dict(
dict(tax_rate=tax_rate, tax_amount=tax_amount)
)
if with_tax_account:
itemised_tax[item_code][tax.description].tax_account = tax.account_head
return itemised_tax
def get_itemised_taxable_amount(items):
itemised_taxable_amount = frappe._dict()
for item in items:
@@ -834,16 +986,18 @@ def get_itemised_taxable_amount(items):
return itemised_taxable_amount
def get_rounded_tax_amount(itemised_tax, precision):
# Rounding based on tax_amount precision
for taxes in itemised_tax.values():
for tax_account in taxes:
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
class init_landed_taxes_and_totals(object):
def __init__(self, doc):
self.doc = doc
self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
self.tax_field = "taxes" if self.doc.doctype == "Landed Cost Voucher" else "additional_costs"
self.set_account_currency()
self.set_exchange_rate()
self.set_amounts_in_company_currency()
@@ -852,7 +1006,7 @@ class init_landed_taxes_and_totals(object):
company_currency = erpnext.get_company_currency(self.doc.company)
for d in self.doc.get(self.tax_field):
if not d.account_currency:
account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
account_currency = frappe.db.get_value("Account", d.expense_account, "account_currency")
d.account_currency = account_currency or company_currency
def set_exchange_rate(self):
@@ -861,8 +1015,12 @@ class init_landed_taxes_and_totals(object):
if d.account_currency == company_currency:
d.exchange_rate = 1
elif not d.exchange_rate:
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
account_currency=d.account_currency, company=self.doc.company)
d.exchange_rate = get_exchange_rate(
self.doc.posting_date,
account=d.expense_account,
account_currency=d.account_currency,
company=self.doc.company,
)
if not d.exchange_rate:
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))

View File

@@ -1,4 +1,3 @@
import json
import unittest
@@ -14,11 +13,12 @@ from erpnext.stock.doctype.quality_inspection.test_quality_inspection import (
class TestItemVariant(unittest.TestCase):
def test_tables_in_template_copied_to_variant(self):
fields = [{'field_name': 'quality_inspection_template'}]
fields = [{"field_name": "quality_inspection_template"}]
set_item_variant_settings(fields)
variant = make_item_variant()
self.assertEqual(variant.get("quality_inspection_template"), "_Test QC Template")
def create_variant_with_tables(item, args):
if isinstance(args, string_types):
args = json.loads(args)
@@ -29,14 +29,11 @@ def create_variant_with_tables(item, args):
template.save()
variant = frappe.new_doc("Item")
variant.variant_based_on = 'Item Attribute'
variant.variant_based_on = "Item Attribute"
variant_attributes = []
for d in template.attributes:
variant_attributes.append({
"attribute": d.attribute,
"attribute_value": args.get(d.attribute)
})
variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)})
variant.set("attributes", variant_attributes)
copy_attributes_to_variant(template, variant)
@@ -44,6 +41,7 @@ def create_variant_with_tables(item, args):
return variant
def make_item_variant():
frappe.delete_doc_if_exists("Item", "_Test Variant Item-XSL", force=1)
variant = create_variant_with_tables("_Test Variant Item", '{"Test Size": "Extra Small"}')
@@ -52,6 +50,7 @@ def make_item_variant():
variant.save()
return variant
def make_quality_inspection_template():
qc_template = "_Test QC Template"
if frappe.db.exists("Quality Inspection Template", qc_template):
@@ -61,10 +60,13 @@ def make_quality_inspection_template():
qc.quality_inspection_template_name = qc_template
create_quality_inspection_parameter("Moisture")
qc.append('item_quality_inspection_parameter', {
"specification": "Moisture",
"value": "&lt; 5%",
})
qc.append(
"item_quality_inspection_parameter",
{
"specification": "Moisture",
"value": "&lt; 5%",
},
)
qc.insert()
return qc.name

View File

@@ -1,4 +1,3 @@
import json
import unittest
@@ -11,14 +10,14 @@ from frappe.utils import add_months, nowdate
class TestMapper(unittest.TestCase):
def test_map_docs(self):
'''Test mapping of multiple source docs on a single target doc'''
"""Test mapping of multiple source docs on a single target doc"""
make_test_records("Item")
items = ['_Test Item', '_Test Item 2', '_Test FG Item']
items = ["_Test Item", "_Test Item 2", "_Test FG Item"]
# Make source docs (quotations) and a target doc (sales order)
qtn1, item_list_1 = self.make_quotation(items, '_Test Customer')
qtn2, item_list_2 = self.make_quotation(items, '_Test Customer')
qtn1, item_list_1 = self.make_quotation(items, "_Test Customer")
qtn2, item_list_2 = self.make_quotation(items, "_Test Customer")
so, item_list_3 = self.make_sales_order()
# Map source docs to target with corresponding mapper method
@@ -27,20 +26,20 @@ class TestMapper(unittest.TestCase):
# Assert that all inserted items are present in updated sales order
src_items = item_list_1 + item_list_2 + item_list_3
self.assertEqual(set(d for d in src_items),
set(d.item_code for d in updated_so.items))
self.assertEqual(set(d for d in src_items), set(d.item_code for d in updated_so.items))
def make_quotation(self, item_list, customer):
qtn = frappe.get_doc({
"doctype": "Quotation",
"quotation_to": "Customer",
"party_name": customer,
"order_type": "Sales",
"transaction_date" : nowdate(),
"valid_till" : add_months(nowdate(), 1)
})
qtn = frappe.get_doc(
{
"doctype": "Quotation",
"quotation_to": "Customer",
"party_name": customer,
"order_type": "Sales",
"transaction_date": nowdate(),
"valid_till": add_months(nowdate(), 1),
}
)
for item in item_list:
qtn.append("items", {"qty": "2", "item_code": item})
@@ -48,21 +47,23 @@ class TestMapper(unittest.TestCase):
return qtn, item_list
def make_sales_order(self):
item = frappe.get_doc({
"base_amount": 1000.0,
"base_rate": 100.0,
"description": "CPU",
"doctype": "Sales Order Item",
"item_code": "_Test Item",
"item_name": "CPU",
"parentfield": "items",
"qty": 10.0,
"rate": 100.0,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": "_Test UOM",
"conversion_factor": 1.0,
"uom": "_Test UOM"
})
so = frappe.get_doc(frappe.get_test_records('Sales Order')[0])
item = frappe.get_doc(
{
"base_amount": 1000.0,
"base_rate": 100.0,
"description": "CPU",
"doctype": "Sales Order Item",
"item_code": "_Test Item",
"item_name": "CPU",
"parentfield": "items",
"qty": 10.0,
"rate": 100.0,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": "_Test UOM",
"conversion_factor": 1.0,
"uom": "_Test UOM",
}
)
so = frappe.get_doc(frappe.get_test_records("Sales Order")[0])
so.insert(ignore_permissions=True)
return so, [item.item_code]

View File

@@ -1,4 +1,3 @@
import unittest
from uuid import uuid4 as _uuid4
@@ -6,104 +5,131 @@ import frappe
def uuid4():
return str(_uuid4())
return str(_uuid4())
class TestTaxes(unittest.TestCase):
def setUp(self):
self.company = frappe.get_doc({
'doctype': 'Company',
'company_name': uuid4(),
'abbr': ''.join(s[0] for s in uuid4().split('-')),
'default_currency': 'USD',
'country': 'United States',
}).insert()
self.account = frappe.get_doc({
'doctype': 'Account',
'account_name': uuid4(),
'account_type': 'Tax',
'company': self.company.name,
'parent_account': 'Duties and Taxes - {self.company.abbr}'.format(self=self)
}).insert()
self.item_group = frappe.get_doc({
'doctype': 'Item Group',
'item_group_name': uuid4(),
'parent_item_group': 'All Item Groups',
}).insert()
self.item_tax_template = frappe.get_doc({
'doctype': 'Item Tax Template',
'title': uuid4(),
'company': self.company.name,
'taxes': [
{
'tax_type': self.account.name,
'tax_rate': 2,
}
]
}).insert()
self.item = frappe.get_doc({
'doctype': 'Item',
'item_code': uuid4(),
'item_group': self.item_group.name,
'is_stock_item': 0,
'taxes': [
{
'item_tax_template': self.item_tax_template.name,
'tax_category': '',
}
],
}).insert()
self.customer = frappe.get_doc({
'doctype': 'Customer',
'customer_name': uuid4(),
'customer_group': 'All Customer Groups',
}).insert()
self.supplier = frappe.get_doc({
'doctype': 'Supplier',
'supplier_name': uuid4(),
'supplier_group': 'All Supplier Groups',
}).insert()
def setUp(self):
self.company = frappe.get_doc(
{
"doctype": "Company",
"company_name": uuid4(),
"abbr": "".join(s[0] for s in uuid4().split("-")),
"default_currency": "USD",
"country": "United States",
}
).insert()
self.account = frappe.get_doc(
{
"doctype": "Account",
"account_name": uuid4(),
"account_type": "Tax",
"company": self.company.name,
"parent_account": "Duties and Taxes - {self.company.abbr}".format(self=self),
}
).insert()
self.item_group = frappe.get_doc(
{
"doctype": "Item Group",
"item_group_name": uuid4(),
"parent_item_group": "All Item Groups",
}
).insert()
self.item_tax_template = frappe.get_doc(
{
"doctype": "Item Tax Template",
"title": uuid4(),
"company": self.company.name,
"taxes": [
{
"tax_type": self.account.name,
"tax_rate": 2,
}
],
}
).insert()
self.item = frappe.get_doc(
{
"doctype": "Item",
"item_code": uuid4(),
"item_group": self.item_group.name,
"is_stock_item": 0,
"taxes": [
{
"item_tax_template": self.item_tax_template.name,
"tax_category": "",
}
],
}
).insert()
self.customer = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": uuid4(),
"customer_group": "All Customer Groups",
}
).insert()
self.supplier = frappe.get_doc(
{
"doctype": "Supplier",
"supplier_name": uuid4(),
"supplier_group": "All Supplier Groups",
}
).insert()
def test_taxes(self):
self.created_docs = []
for dt in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice',
'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']:
doc = frappe.get_doc({
'doctype': dt,
'company': self.company.name,
'supplier': self.supplier.name,
'currency': "USD",
'schedule_date': frappe.utils.nowdate(),
'delivery_date': frappe.utils.nowdate(),
'customer': self.customer.name,
'buying_price_list' if dt.startswith('Purchase') else 'selling_price_list'
: 'Standard Buying' if dt.startswith('Purchase') else 'Standard Selling',
'items': [
{
'item_code': self.item.name,
'qty': 300,
'rate': 100,
}
],
'taxes': [
{
'charge_type': 'On Item Quantity',
'account_head': self.account.name,
'description': 'N/A',
'rate': 0,
},
],
})
doc.run_method('set_missing_values')
doc.run_method('calculate_taxes_and_totals')
doc.insert()
self.assertEqual(doc.taxes[0].tax_amount, 600)
self.created_docs.append(doc)
def test_taxes(self):
self.created_docs = []
for dt in [
"Purchase Order",
"Purchase Receipt",
"Purchase Invoice",
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
]:
doc = frappe.get_doc(
{
"doctype": dt,
"company": self.company.name,
"supplier": self.supplier.name,
"currency": "USD",
"schedule_date": frappe.utils.nowdate(),
"delivery_date": frappe.utils.nowdate(),
"customer": self.customer.name,
"buying_price_list"
if dt.startswith("Purchase")
else "selling_price_list": "Standard Buying"
if dt.startswith("Purchase")
else "Standard Selling",
"items": [
{
"item_code": self.item.name,
"qty": 300,
"rate": 100,
}
],
"taxes": [
{
"charge_type": "On Item Quantity",
"account_head": self.account.name,
"description": "N/A",
"rate": 0,
},
],
}
)
doc.run_method("set_missing_values")
doc.run_method("calculate_taxes_and_totals")
doc.insert()
self.assertEqual(doc.taxes[0].tax_amount, 600)
self.created_docs.append(doc)
def tearDown(self):
for doc in self.created_docs:
doc.delete()
self.item.delete()
self.item_group.delete()
self.item_tax_template.delete()
self.account.delete()
self.company.delete()
def tearDown(self):
for doc in self.created_docs:
doc.delete()
self.item.delete()
self.item_group.delete()
self.item_tax_template.delete()
self.account.delete()
self.company.delete()

View File

@@ -5,18 +5,28 @@ import frappe
class TestUtils(unittest.TestCase):
def test_reset_default_field_value(self):
doc = frappe.get_doc({
"doctype": "Purchase Receipt",
"set_warehouse": "Warehouse 1",
})
doc = frappe.get_doc(
{
"doctype": "Purchase Receipt",
"set_warehouse": "Warehouse 1",
}
)
# Same values
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
doc.items = [
{"warehouse": "Warehouse 1"},
{"warehouse": "Warehouse 1"},
{"warehouse": "Warehouse 1"},
]
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
self.assertEqual(doc.set_warehouse, "Warehouse 1")
# Mixed values
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
doc.items = [
{"warehouse": "Warehouse 1"},
{"warehouse": "Warehouse 2"},
{"warehouse": "Warehouse 1"},
]
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
self.assertEqual(doc.set_warehouse, None)
@@ -30,9 +40,13 @@ class TestUtils(unittest.TestCase):
from_warehouse="_Test Warehouse - _TC",
to_warehouse="_Test Warehouse 1 - _TC",
items=[
frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1)
]
frappe._dict(
item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"
),
frappe._dict(
item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1
),
],
)
se.save()
@@ -43,18 +57,20 @@ class TestUtils(unittest.TestCase):
se.delete()
def test_reset_default_field_value_in_transfer_stock_entry(self):
doc = frappe.get_doc({
"doctype": "Stock Entry",
"purpose": "Material Receipt",
"from_warehouse": "Warehouse 1",
"to_warehouse": "Warehouse 2",
})
doc = frappe.get_doc(
{
"doctype": "Stock Entry",
"purpose": "Material Receipt",
"from_warehouse": "Warehouse 1",
"to_warehouse": "Warehouse 2",
}
)
# Same values
doc.items = [
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
]
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
@@ -66,10 +82,10 @@ class TestUtils(unittest.TestCase):
doc.items = [
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
{"s_warehouse": "Warehouse 3", "t_warehouse": "Warehouse 2"},
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
]
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
self.assertEqual(doc.from_warehouse, None)
self.assertEqual(doc.to_warehouse, "Warehouse 2")
self.assertEqual(doc.to_warehouse, "Warehouse 2")

View File

@@ -17,17 +17,33 @@ def get_columns(filters, trans):
# get conditions for grouping filter cond
group_by_cols = group_wise_column(filters.get("group_by"))
columns = based_on_details["based_on_cols"] + period_cols + [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
columns = (
based_on_details["based_on_cols"]
+ period_cols
+ [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
)
if group_by_cols:
columns = based_on_details["based_on_cols"] + group_by_cols + period_cols + \
[_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
columns = (
based_on_details["based_on_cols"]
+ group_by_cols
+ period_cols
+ [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
)
conditions = {"based_on_select": based_on_details["based_on_select"], "period_wise_select": period_select,
"columns": columns, "group_by": based_on_details["based_on_group_by"], "grbc": group_by_cols, "trans": trans,
"addl_tables": based_on_details["addl_tables"], "addl_tables_relational_cond": based_on_details.get("addl_tables_relational_cond", "")}
conditions = {
"based_on_select": based_on_details["based_on_select"],
"period_wise_select": period_select,
"columns": columns,
"group_by": based_on_details["based_on_group_by"],
"grbc": group_by_cols,
"trans": trans,
"addl_tables": based_on_details["addl_tables"],
"addl_tables_relational_cond": based_on_details.get("addl_tables_relational_cond", ""),
}
return conditions
def validate_filters(filters):
for f in ["Fiscal Year", "Based On", "Period", "Company"]:
if not filters.get(f.lower().replace(" ", "_")):
@@ -39,153 +55,231 @@ def validate_filters(filters):
if filters.get("based_on") == filters.get("group_by"):
frappe.throw(_("'Based On' and 'Group By' can not be same"))
def get_data(filters, conditions):
data = []
inc, cond= '',''
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
inc, cond = "", ""
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
posting_date = 't1.transaction_date'
if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']:
posting_date = 't1.posting_date'
posting_date = "t1.transaction_date"
if conditions.get("trans") in [
"Sales Invoice",
"Purchase Invoice",
"Purchase Receipt",
"Delivery Note",
]:
posting_date = "t1.posting_date"
if filters.period_based_on:
posting_date = 't1.'+filters.period_based_on
posting_date = "t1." + filters.period_based_on
if conditions["based_on_select"] in ["t1.project,", "t2.project,"]:
cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
if conditions.get('trans') in ['Sales Order', 'Purchase Order']:
cond = " and " + conditions["based_on_select"][:-1] + " IS Not NULL"
if conditions.get("trans") in ["Sales Order", "Purchase Order"]:
cond += " and t1.status != 'Closed'"
if conditions.get('trans') == 'Quotation' and filters.get("group_by") == 'Customer':
if conditions.get("trans") == "Quotation" and filters.get("group_by") == "Customer":
cond += " and t1.quotation_to = 'Customer'"
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
filters.get('fiscal_year'), ["year_start_date", "year_end_date"])
year_start_date, year_end_date = frappe.db.get_value(
"Fiscal Year", filters.get("fiscal_year"), ["year_start_date", "year_end_date"]
)
if filters.get("group_by"):
sel_col = ''
sel_col = ""
ind = conditions["columns"].index(conditions["grbc"][0])
if filters.get("group_by") == 'Item':
sel_col = 't2.item_code'
elif filters.get("group_by") == 'Customer':
sel_col = 't1.party_name' if conditions.get('trans') == 'Quotation' else 't1.customer'
elif filters.get("group_by") == 'Supplier':
sel_col = 't1.supplier'
if filters.get("group_by") == "Item":
sel_col = "t2.item_code"
elif filters.get("group_by") == "Customer":
sel_col = "t1.party_name" if conditions.get("trans") == "Quotation" else "t1.customer"
elif filters.get("group_by") == "Supplier":
sel_col = "t1.supplier"
if filters.get('based_on') in ['Item','Customer','Supplier']:
if filters.get("based_on") in ["Item", "Customer", "Supplier"]:
inc = 2
else :
else:
inc = 1
data1 = frappe.db.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
data1 = frappe.db.sql(
""" select %s from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s and
t1.docstatus = 1 %s %s
group by %s
""" % (query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"], "%s",
posting_date, "%s", "%s", conditions.get("addl_tables_relational_cond"), cond, conditions["group_by"]), (filters.get("company"),
year_start_date, year_end_date),as_list=1)
"""
% (
query_details,
conditions["trans"],
conditions["trans"],
conditions["addl_tables"],
"%s",
posting_date,
"%s",
"%s",
conditions.get("addl_tables_relational_cond"),
cond,
conditions["group_by"],
),
(filters.get("company"), year_start_date, year_end_date),
as_list=1,
)
for d in range(len(data1)):
#to add blanck column
# to add blanck column
dt = data1[d]
dt.insert(ind,'')
dt.insert(ind, "")
data.append(dt)
#to get distinct value of col specified by group_by in filter
row = frappe.db.sql("""select DISTINCT(%s) from `tab%s` t1, `tab%s Item` t2 %s
# to get distinct value of col specified by group_by in filter
row = frappe.db.sql(
"""select DISTINCT(%s) from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s
and t1.docstatus = 1 and %s = %s %s %s
""" %
(sel_col, conditions["trans"], conditions["trans"], conditions["addl_tables"],
"%s", posting_date, "%s", "%s", conditions["group_by"], "%s", conditions.get("addl_tables_relational_cond"), cond),
(filters.get("company"), year_start_date, year_end_date, data1[d][0]), as_list=1)
"""
% (
sel_col,
conditions["trans"],
conditions["trans"],
conditions["addl_tables"],
"%s",
posting_date,
"%s",
"%s",
conditions["group_by"],
"%s",
conditions.get("addl_tables_relational_cond"),
cond,
),
(filters.get("company"), year_start_date, year_end_date, data1[d][0]),
as_list=1,
)
for i in range(len(row)):
des = ['' for q in range(len(conditions["columns"]))]
des = ["" for q in range(len(conditions["columns"]))]
#get data for group_by filter
row1 = frappe.db.sql(""" select %s , %s from `tab%s` t1, `tab%s Item` t2 %s
# get data for group_by filter
row1 = frappe.db.sql(
""" select %s , %s from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s
and t1.docstatus = 1 and %s = %s and %s = %s %s %s
""" %
(sel_col, conditions["period_wise_select"], conditions["trans"],
conditions["trans"], conditions["addl_tables"], "%s", posting_date, "%s","%s", sel_col,
"%s", conditions["group_by"], "%s", conditions.get("addl_tables_relational_cond"), cond),
(filters.get("company"), year_start_date, year_end_date, row[i][0],
data1[d][0]), as_list=1)
"""
% (
sel_col,
conditions["period_wise_select"],
conditions["trans"],
conditions["trans"],
conditions["addl_tables"],
"%s",
posting_date,
"%s",
"%s",
sel_col,
"%s",
conditions["group_by"],
"%s",
conditions.get("addl_tables_relational_cond"),
cond,
),
(filters.get("company"), year_start_date, year_end_date, row[i][0], data1[d][0]),
as_list=1,
)
des[ind] = row[i][0]
for j in range(1,len(conditions["columns"])-inc):
des[j+inc] = row1[0][j]
for j in range(1, len(conditions["columns"]) - inc):
des[j + inc] = row1[0][j]
data.append(des)
else:
data = frappe.db.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
data = frappe.db.sql(
""" select %s from `tab%s` t1, `tab%s Item` t2 %s
where t2.parent = t1.name and t1.company = %s and %s between %s and %s and
t1.docstatus = 1 %s %s
group by %s
""" %
(query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"],
"%s", posting_date, "%s", "%s", cond, conditions.get("addl_tables_relational_cond", ""), conditions["group_by"]),
(filters.get("company"), year_start_date, year_end_date), as_list=1)
"""
% (
query_details,
conditions["trans"],
conditions["trans"],
conditions["addl_tables"],
"%s",
posting_date,
"%s",
"%s",
cond,
conditions.get("addl_tables_relational_cond", ""),
conditions["group_by"],
),
(filters.get("company"), year_start_date, year_end_date),
as_list=1,
)
return data
def get_mon(dt):
return getdate(dt).strftime("%b")
def period_wise_columns_query(filters, trans):
query_details = ''
query_details = ""
pwc = []
bet_dates = get_period_date_ranges(filters.get("period"), filters.get("fiscal_year"))
if trans in ['Purchase Receipt', 'Delivery Note', 'Purchase Invoice', 'Sales Invoice']:
trans_date = 'posting_date'
if trans in ["Purchase Receipt", "Delivery Note", "Purchase Invoice", "Sales Invoice"]:
trans_date = "posting_date"
if filters.period_based_on:
trans_date = filters.period_based_on
else:
trans_date = 'transaction_date'
trans_date = "transaction_date"
if filters.get("period") != 'Yearly':
if filters.get("period") != "Yearly":
for dt in bet_dates:
get_period_wise_columns(dt, filters.get("period"), pwc)
query_details = get_period_wise_query(dt, trans_date, query_details)
else:
pwc = [_(filters.get("fiscal_year")) + " ("+_("Qty") + "):Float:120",
_(filters.get("fiscal_year")) + " ("+ _("Amt") + "):Currency:120"]
pwc = [
_(filters.get("fiscal_year")) + " (" + _("Qty") + "):Float:120",
_(filters.get("fiscal_year")) + " (" + _("Amt") + "):Currency:120",
]
query_details = " SUM(t2.stock_qty), SUM(t2.base_net_amount),"
query_details += 'SUM(t2.stock_qty), SUM(t2.base_net_amount)'
query_details += "SUM(t2.stock_qty), SUM(t2.base_net_amount)"
return pwc, query_details
def get_period_wise_columns(bet_dates, period, pwc):
if period == 'Monthly':
pwc += [_(get_mon(bet_dates[0])) + " (" + _("Qty") + "):Float:120",
_(get_mon(bet_dates[0])) + " (" + _("Amt") + "):Currency:120"]
if period == "Monthly":
pwc += [
_(get_mon(bet_dates[0])) + " (" + _("Qty") + "):Float:120",
_(get_mon(bet_dates[0])) + " (" + _("Amt") + "):Currency:120",
]
else:
pwc += [_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Qty") + "):Float:120",
_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Amt") + "):Currency:120"]
pwc += [
_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Qty") + "):Float:120",
_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Amt") + "):Currency:120",
]
def get_period_wise_query(bet_dates, trans_date, query_details):
query_details += """SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t2.stock_qty, NULL)),
SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t2.base_net_amount, NULL)),
""" % {"trans_date": trans_date, "sd": bet_dates[0],"ed": bet_dates[1]}
""" % {
"trans_date": trans_date,
"sd": bet_dates[0],
"ed": bet_dates[1],
}
return query_details
@frappe.whitelist(allow_guest=True)
def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
from dateutil.relativedelta import relativedelta
if not year_start_date:
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
fiscal_year, ["year_start_date", "year_end_date"])
year_start_date, year_end_date = frappe.db.get_value(
"Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
)
increment = {
"Monthly": 1,
"Quarterly": 3,
"Half-Yearly": 6,
"Yearly": 12
}.get(period)
increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get(period)
period_date_ranges = []
for i in range(1, 13, increment):
@@ -199,8 +293,10 @@ def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
return period_date_ranges
def get_period_month_ranges(period, fiscal_year):
from dateutil.relativedelta import relativedelta
period_month_ranges = []
for start_date, end_date in get_period_date_ranges(period, fiscal_year):
@@ -212,6 +308,7 @@ def get_period_month_ranges(period, fiscal_year):
return period_month_ranges
def based_wise_columns_query(based_on, trans):
based_on_details = {}
@@ -219,65 +316,74 @@ def based_wise_columns_query(based_on, trans):
if based_on == "Item":
based_on_details["based_on_cols"] = ["Item:Link/Item:120", "Item Name:Data:120"]
based_on_details["based_on_select"] = "t2.item_code, t2.item_name,"
based_on_details["based_on_group_by"] = 't2.item_code'
based_on_details["addl_tables"] = ''
based_on_details["based_on_group_by"] = "t2.item_code"
based_on_details["addl_tables"] = ""
elif based_on == "Item Group":
based_on_details["based_on_cols"] = ["Item Group:Link/Item Group:120"]
based_on_details["based_on_select"] = "t2.item_group,"
based_on_details["based_on_group_by"] = 't2.item_group'
based_on_details["addl_tables"] = ''
based_on_details["based_on_group_by"] = "t2.item_group"
based_on_details["addl_tables"] = ""
elif based_on == "Customer":
based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"]
based_on_details["based_on_cols"] = [
"Customer:Link/Customer:120",
"Territory:Link/Territory:120",
]
based_on_details["based_on_select"] = "t1.customer_name, t1.territory, "
based_on_details["based_on_group_by"] = 't1.party_name' if trans == 'Quotation' else 't1.customer'
based_on_details["addl_tables"] = ''
based_on_details["based_on_group_by"] = (
"t1.party_name" if trans == "Quotation" else "t1.customer"
)
based_on_details["addl_tables"] = ""
elif based_on == "Customer Group":
based_on_details["based_on_cols"] = ["Customer Group:Link/Customer Group"]
based_on_details["based_on_select"] = "t1.customer_group,"
based_on_details["based_on_group_by"] = 't1.customer_group'
based_on_details["addl_tables"] = ''
based_on_details["based_on_group_by"] = "t1.customer_group"
based_on_details["addl_tables"] = ""
elif based_on == 'Supplier':
based_on_details["based_on_cols"] = ["Supplier:Link/Supplier:120", "Supplier Group:Link/Supplier Group:140"]
elif based_on == "Supplier":
based_on_details["based_on_cols"] = [
"Supplier:Link/Supplier:120",
"Supplier Group:Link/Supplier Group:140",
]
based_on_details["based_on_select"] = "t1.supplier, t3.supplier_group,"
based_on_details["based_on_group_by"] = 't1.supplier'
based_on_details["addl_tables"] = ',`tabSupplier` t3'
based_on_details["based_on_group_by"] = "t1.supplier"
based_on_details["addl_tables"] = ",`tabSupplier` t3"
based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name"
elif based_on == 'Supplier Group':
elif based_on == "Supplier Group":
based_on_details["based_on_cols"] = ["Supplier Group:Link/Supplier Group:140"]
based_on_details["based_on_select"] = "t3.supplier_group,"
based_on_details["based_on_group_by"] = 't3.supplier_group'
based_on_details["addl_tables"] = ',`tabSupplier` t3'
based_on_details["based_on_group_by"] = "t3.supplier_group"
based_on_details["addl_tables"] = ",`tabSupplier` t3"
based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name"
elif based_on == "Territory":
based_on_details["based_on_cols"] = ["Territory:Link/Territory:120"]
based_on_details["based_on_select"] = "t1.territory,"
based_on_details["based_on_group_by"] = 't1.territory'
based_on_details["addl_tables"] = ''
based_on_details["based_on_group_by"] = "t1.territory"
based_on_details["addl_tables"] = ""
elif based_on == "Project":
if trans in ['Sales Invoice', 'Delivery Note', 'Sales Order']:
if trans in ["Sales Invoice", "Delivery Note", "Sales Order"]:
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
based_on_details["based_on_select"] = "t1.project,"
based_on_details["based_on_group_by"] = 't1.project'
based_on_details["addl_tables"] = ''
elif trans in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
based_on_details["based_on_group_by"] = "t1.project"
based_on_details["addl_tables"] = ""
elif trans in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]:
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
based_on_details["based_on_select"] = "t2.project,"
based_on_details["based_on_group_by"] = 't2.project'
based_on_details["addl_tables"] = ''
based_on_details["based_on_group_by"] = "t2.project"
based_on_details["addl_tables"] = ""
else:
frappe.throw(_("Project-wise data is not available for Quotation"))
return based_on_details
def group_wise_column(group_by):
if group_by:
return [group_by+":Link/"+group_by+":120"]
return [group_by + ":Link/" + group_by + ":120"]
else:
return []

View File

@@ -15,21 +15,29 @@ def get_list_context(context=None):
return {
"global_number_format": frappe.db.get_default("number_format") or "#,###.##",
"currency": frappe.db.get_default("currency"),
"currency_symbols": json.dumps(dict(frappe.db.sql("""select name, symbol
from tabCurrency where enabled=1"""))),
"currency_symbols": json.dumps(
dict(
frappe.db.sql(
"""select name, symbol
from tabCurrency where enabled=1"""
)
)
),
"row_template": "templates/includes/transaction_row.html",
"get_list": get_transaction_list
"get_list": get_transaction_list,
}
def get_webform_list_context(module):
if get_module_app(module) != 'erpnext':
if get_module_app(module) != "erpnext":
return
return {
"get_list": get_webform_transaction_list
}
return {"get_list": get_webform_transaction_list}
def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
""" Get List of transactions for custom doctypes """
def get_webform_transaction_list(
doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"
):
"""Get List of transactions for custom doctypes"""
from frappe.www.list import get_list
if not filters:
@@ -38,42 +46,62 @@ def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0,
meta = frappe.get_meta(doctype)
for d in meta.fields:
if d.fieldtype == 'Link' and d.fieldname != 'amended_from':
if d.fieldtype == "Link" and d.fieldname != "amended_from":
allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)]
allowed_docs.append('')
filters.append((d.fieldname, 'in', allowed_docs))
allowed_docs.append("")
filters.append((d.fieldname, "in", allowed_docs))
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False,
fields=None, order_by="modified")
return get_list(
doctype,
txt,
filters,
limit_start,
limit_page_length,
ignore_permissions=False,
fields=None,
order_by="modified",
)
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False):
def get_transaction_list(
doctype,
txt=None,
filters=None,
limit_start=0,
limit_page_length=20,
order_by="modified",
custom=False,
):
user = frappe.session.user
ignore_permissions = False
if not filters: filters = []
if not filters:
filters = []
if doctype in ['Supplier Quotation', 'Purchase Invoice']:
filters.append((doctype, 'docstatus', '<', 2))
if doctype in ["Supplier Quotation", "Purchase Invoice"]:
filters.append((doctype, "docstatus", "<", 2))
else:
filters.append((doctype, 'docstatus', '=', 1))
filters.append((doctype, "docstatus", "=", 1))
if (user != 'Guest' and is_website_user()) or doctype == 'Request for Quotation':
parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
if (user != "Guest" and is_website_user()) or doctype == "Request for Quotation":
parties_doctype = (
"Request for Quotation Supplier" if doctype == "Request for Quotation" else doctype
)
# find party for this contact
customers, suppliers = get_customers_suppliers(parties_doctype, user)
if customers:
if doctype == 'Quotation':
filters.append(('quotation_to', '=', 'Customer'))
filters.append(('party_name', 'in', customers))
if doctype == "Quotation":
filters.append(("quotation_to", "=", "Customer"))
filters.append(("party_name", "in", customers))
else:
filters.append(('customer', 'in', customers))
filters.append(("customer", "in", customers))
elif suppliers:
filters.append(('supplier', 'in', suppliers))
filters.append(("supplier", "in", suppliers))
elif not custom:
return []
if doctype == 'Request for Quotation':
if doctype == "Request for Quotation":
parties = customers or suppliers
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
@@ -84,49 +112,88 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
ignore_permissions = False
filters = []
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
transactions = get_list_for_transactions(
doctype,
txt,
filters,
limit_start,
limit_page_length,
fields="name",
ignore_permissions=ignore_permissions,
order_by="modified desc",
)
if custom:
return transactions
return post_process(doctype, transactions)
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
ignore_permissions=False, fields=None, order_by=None):
""" Get List of transactions like Invoices, Orders """
def get_list_for_transactions(
doctype,
txt,
filters,
limit_start,
limit_page_length=20,
ignore_permissions=False,
fields=None,
order_by=None,
):
"""Get List of transactions like Invoices, Orders"""
from frappe.www.list import get_list
meta = frappe.get_meta(doctype)
data = []
or_filters = []
for d in get_list(doctype, txt, filters=filters, fields="name", limit_start=limit_start,
limit_page_length=limit_page_length, ignore_permissions=ignore_permissions, order_by="modified desc"):
for d in get_list(
doctype,
txt,
filters=filters,
fields="name",
limit_start=limit_start,
limit_page_length=limit_page_length,
ignore_permissions=ignore_permissions,
order_by="modified desc",
):
data.append(d)
if txt:
if meta.get_field('items'):
if meta.get_field('items').options:
child_doctype = meta.get_field('items').options
for item in frappe.get_all(child_doctype, {"item_name": ['like', "%" + txt + "%"]}):
if meta.get_field("items"):
if meta.get_field("items").options:
child_doctype = meta.get_field("items").options
for item in frappe.get_all(child_doctype, {"item_name": ["like", "%" + txt + "%"]}):
child = frappe.get_doc(child_doctype, item.name)
or_filters.append([doctype, "name", "=", child.parent])
if or_filters:
for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters,
limit_start=limit_start, limit_page_length=limit_page_length,
ignore_permissions=ignore_permissions, order_by=order_by):
for r in frappe.get_list(
doctype,
fields=fields,
filters=filters,
or_filters=or_filters,
limit_start=limit_start,
limit_page_length=limit_page_length,
ignore_permissions=ignore_permissions,
order_by=order_by,
):
data.append(r)
return data
def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length):
data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}`
where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".
format(doctype=parties_doctype, supplier=parties[0], start=limit_start, len = limit_page_length), as_dict=1)
data = frappe.db.sql(
"""select distinct parent as name, supplier from `tab{doctype}`
where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".format(
doctype=parties_doctype, supplier=parties[0], start=limit_start, len=limit_page_length
),
as_dict=1,
)
return post_process(doctype, data)
def post_process(doctype, data):
result = []
for d in data:
@@ -137,11 +204,15 @@ def post_process(doctype, data):
if doc.get("per_billed"):
doc.status_percent += flt(doc.per_billed)
doc.status_display.append(_("Billed") if doc.per_billed==100 else _("{0}% Billed").format(doc.per_billed))
doc.status_display.append(
_("Billed") if doc.per_billed == 100 else _("{0}% Billed").format(doc.per_billed)
)
if doc.get("per_delivered"):
doc.status_percent += flt(doc.per_delivered)
doc.status_display.append(_("Delivered") if doc.per_delivered==100 else _("{0}% Delivered").format(doc.per_delivered))
doc.status_display.append(
_("Delivered") if doc.per_delivered == 100 else _("{0}% Delivered").format(doc.per_delivered)
)
if hasattr(doc, "set_indicator"):
doc.set_indicator()
@@ -152,6 +223,7 @@ def post_process(doctype, data):
return result
def get_customers_suppliers(doctype, user):
customers = []
suppliers = []
@@ -160,10 +232,11 @@ def get_customers_suppliers(doctype, user):
customer_field_name = get_customer_field_name(doctype)
has_customer_field = meta.has_field(customer_field_name)
has_supplier_field = meta.has_field('supplier')
has_supplier_field = meta.has_field("supplier")
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
contacts = frappe.db.sql("""
contacts = frappe.db.sql(
"""
select
`tabContact`.email_id,
`tabDynamic Link`.link_doctype,
@@ -172,15 +245,18 @@ def get_customers_suppliers(doctype, user):
`tabContact`, `tabDynamic Link`
where
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
""", user, as_dict=1)
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
elif frappe.has_permission(doctype, 'read', user=user):
""",
user,
as_dict=1,
)
customers = [c.link_name for c in contacts if c.link_doctype == "Customer"]
suppliers = [c.link_name for c in contacts if c.link_doctype == "Supplier"]
elif frappe.has_permission(doctype, "read", user=user):
customer_list = frappe.get_list("Customer")
customers = suppliers = [customer.name for customer in customer_list]
return customers if has_customer_field else None, \
suppliers if has_supplier_field else None
return customers if has_customer_field else None, suppliers if has_supplier_field else None
def has_website_permission(doc, ptype, user, verbose=False):
doctype = doc.doctype
@@ -188,25 +264,24 @@ def has_website_permission(doc, ptype, user, verbose=False):
if customers:
return frappe.db.exists(doctype, get_customer_filter(doc, customers))
elif suppliers:
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
return frappe.db.exists(doctype, {
'name': doc.name,
fieldname: ["in", suppliers]
})
fieldname = "suppliers" if doctype == "Request for Quotation" else "supplier"
return frappe.db.exists(doctype, {"name": doc.name, fieldname: ["in", suppliers]})
else:
return False
def get_customer_filter(doc, customers):
doctype = doc.doctype
filters = frappe._dict()
filters.name = doc.name
filters[get_customer_field_name(doctype)] = ['in', customers]
if doctype == 'Quotation':
filters.quotation_to = 'Customer'
filters[get_customer_field_name(doctype)] = ["in", customers]
if doctype == "Quotation":
filters.quotation_to = "Customer"
return filters
def get_customer_field_name(doctype):
if doctype == 'Quotation':
return 'party_name'
if doctype == "Quotation":
return "party_name"
else:
return 'customer'
return "customer"