Marica
3 years ago
committed by
GitHub
34 changed files with 664 additions and 336 deletions
@ -1,78 +0,0 @@ |
|||
{ |
|||
"charts": [], |
|||
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Integrations Settings\", \"col\": 4}}]", |
|||
"creation": "2020-07-31 10:38:54.021237", |
|||
"docstatus": 0, |
|||
"doctype": "Workspace", |
|||
"for_user": "", |
|||
"hide_custom": 0, |
|||
"icon": "setting", |
|||
"idx": 0, |
|||
"label": "ERPNext Integrations Settings", |
|||
"links": [ |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Integrations Settings", |
|||
"link_count": 0, |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Woocommerce Settings", |
|||
"link_count": 0, |
|||
"link_to": "Woocommerce Settings", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Amazon MWS Settings", |
|||
"link_count": 0, |
|||
"link_to": "Amazon MWS Settings", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Plaid Settings", |
|||
"link_count": 0, |
|||
"link_to": "Plaid Settings", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Exotel Settings", |
|||
"link_count": 0, |
|||
"link_to": "Exotel Settings", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
} |
|||
], |
|||
"modified": "2021-11-23 04:30:33.106991", |
|||
"modified_by": "Administrator", |
|||
"module": "ERPNext Integrations", |
|||
"name": "ERPNext Integrations Settings", |
|||
"owner": "Administrator", |
|||
"parent_page": "", |
|||
"public": 1, |
|||
"restrict_to_domain": "", |
|||
"roles": [], |
|||
"sequence_id": 11, |
|||
"shortcuts": [], |
|||
"title": "ERPNext Integrations Settings" |
|||
} |
@ -1 +0,0 @@ |
|||
[] |
@ -0,0 +1,31 @@ |
|||
import frappe |
|||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields |
|||
|
|||
from erpnext.regional.india import states |
|||
|
|||
|
|||
def execute(): |
|||
company = frappe.get_all('Company', filters = {'country': 'India'}) |
|||
if not company: |
|||
return |
|||
|
|||
create_custom_fields({ |
|||
'Tax Category': [ |
|||
dict(fieldname='is_inter_state', label='Is Inter State', |
|||
fieldtype='Check', insert_after='disabled', print_hide=1), |
|||
dict(fieldname='is_reverse_charge', label='Is Reverse Charge', fieldtype='Check', |
|||
insert_after='is_inter_state', print_hide=1), |
|||
dict(fieldname='tax_category_column_break', fieldtype='Column Break', |
|||
insert_after='is_reverse_charge'), |
|||
dict(fieldname='gst_state', label='Source State', fieldtype='Select', |
|||
options='\n'.join(states), insert_after='company') |
|||
] |
|||
}, update=True) |
|||
|
|||
tax_category = frappe.qb.DocType("Tax Category") |
|||
|
|||
frappe.qb.update(tax_category).set( |
|||
tax_category.is_reverse_charge, 1 |
|||
).where( |
|||
tax_category.name.isin(['Reverse Charge Out-State', 'Reverse Charge In-State']) |
|||
).run() |
@ -0,0 +1,73 @@ |
|||
### Concept of FIFO Slots |
|||
|
|||
Since we need to know age-wise remaining stock, we maintain all the inward entries as slots. So each time stock comes in, a slot is added for the same. |
|||
|
|||
Eg. For Item A: |
|||
---------------------- |
|||
Date | Qty | Queue |
|||
---------------------- |
|||
1st | +50 | [[50, 1-12-2021]] |
|||
2nd | +20 | [[50, 1-12-2021], [20, 2-12-2021]] |
|||
---------------------- |
|||
|
|||
Now the queue can tell us the total stock and also how old the stock is. |
|||
Here, the balance qty is 70. |
|||
50 qty is (today-the 1st) days old |
|||
20 qty is (today-the 2nd) days old |
|||
|
|||
### Calculation of FIFO Slots |
|||
|
|||
#### Case 1: Outward from sufficient balance qty |
|||
---------------------- |
|||
Date | Qty | Queue |
|||
---------------------- |
|||
1st | +50 | [[50, 1-12-2021]] |
|||
2nd | -20 | [[30, 1-12-2021]] |
|||
2nd | +20 | [[30, 1-12-2021], [20, 2-12-2021]] |
|||
|
|||
Here after the first entry, while issuing 20 qty: |
|||
- **since 20 is lesser than the balance**, **qty_to_pop (20)** is simply consumed from first slot (FIFO consumption) |
|||
- Any inward entry after as usual will get its own slot added to the queue |
|||
|
|||
#### Case 2: Outward from sufficient cumulative (slots) balance qty |
|||
---------------------- |
|||
Date | Qty | Queue |
|||
---------------------- |
|||
1st | +50 | [[50, 1-12-2021]] |
|||
2nd | +20 | [[50, 1-12-2021], [20, 2-12-2021]] |
|||
2nd | -60 | [[10, 2-12-2021]] |
|||
|
|||
- Consumption happens slot wise. First slot 1 is consumed |
|||
- Since **qty_to_pop (60) is greater than slot 1 qty (50)**, the entire slot is consumed and popped |
|||
- Now the queue is [[20, 2-12-2021]], and **qty_to_pop=10** (remaining qty to pop) |
|||
- It then goes ahead to the next slot and consumes 10 from it |
|||
- Now the queue is [[10, 2-12-2021]] |
|||
|
|||
#### Case 3: Outward from insufficient balance qty |
|||
> This case is possible only if **Allow Negative Stock** was enabled at some point/is enabled. |
|||
|
|||
---------------------- |
|||
Date | Qty | Queue |
|||
---------------------- |
|||
1st | +50 | [[50, 1-12-2021]] |
|||
2nd | -60 | [[-10, 1-12-2021]] |
|||
|
|||
- Since **qty_to_pop (60)** is more than the balance in slot 1, the entire slot is consumed and popped |
|||
- Now the queue is **empty**, and **qty_to_pop=10** (remaining qty to pop) |
|||
- Since we still have more to consume, we append the balance since 60 is issued from 50 i.e. -10. |
|||
- We register this negative value, since the stock issue has caused the balance to become negative |
|||
|
|||
Now when stock is inwarded: |
|||
- Instead of adding a slot we check if there are any negative balances. |
|||
- If yes, we keep adding positive stock to it until we make the balance positive. |
|||
- Once the balance is positive, the next inward entry will add a new slot in the queue |
|||
|
|||
Eg: |
|||
---------------------- |
|||
Date | Qty | Queue |
|||
---------------------- |
|||
1st | +50 | [[50, 1-12-2021]] |
|||
2nd | -60 | [[-10, 1-12-2021]] |
|||
3rd | +5 | [[-5, 3-12-2021]] |
|||
4th | +10 | [[5, 4-12-2021]] |
|||
4th | +20 | [[5, 4-12-2021], [20, 4-12-2021]] |
@ -0,0 +1,126 @@ |
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors |
|||
# See license.txt |
|||
|
|||
import frappe |
|||
|
|||
from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots |
|||
from erpnext.tests.utils import ERPNextTestCase |
|||
|
|||
|
|||
class TestStockAgeing(ERPNextTestCase): |
|||
def setUp(self) -> None: |
|||
self.filters = frappe._dict( |
|||
company="_Test Company", |
|||
to_date="2021-12-10" |
|||
) |
|||
|
|||
def test_normal_inward_outward_queue(self): |
|||
"Reference: Case 1 in stock_ageing_fifo_logic.md" |
|||
sle = [ |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=30, qty_after_transaction=30, |
|||
posting_date="2021-12-01", voucher_type="Stock Entry", |
|||
voucher_no="001", |
|||
has_serial_no=False, serial_no=None |
|||
), |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=20, qty_after_transaction=50, |
|||
posting_date="2021-12-02", voucher_type="Stock Entry", |
|||
voucher_no="002", |
|||
has_serial_no=False, serial_no=None |
|||
), |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=(-10), qty_after_transaction=40, |
|||
posting_date="2021-12-03", voucher_type="Stock Entry", |
|||
voucher_no="003", |
|||
has_serial_no=False, serial_no=None |
|||
) |
|||
] |
|||
|
|||
slots = FIFOSlots(self.filters, sle).generate() |
|||
|
|||
self.assertTrue(slots["Flask Item"]["fifo_queue"]) |
|||
result = slots["Flask Item"] |
|||
queue = result["fifo_queue"] |
|||
|
|||
self.assertEqual(result["qty_after_transaction"], result["total_qty"]) |
|||
self.assertEqual(queue[0][0], 20.0) |
|||
|
|||
def test_insufficient_balance(self): |
|||
"Reference: Case 3 in stock_ageing_fifo_logic.md" |
|||
sle = [ |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=(-30), qty_after_transaction=(-30), |
|||
posting_date="2021-12-01", voucher_type="Stock Entry", |
|||
voucher_no="001", |
|||
has_serial_no=False, serial_no=None |
|||
), |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=20, qty_after_transaction=(-10), |
|||
posting_date="2021-12-02", voucher_type="Stock Entry", |
|||
voucher_no="002", |
|||
has_serial_no=False, serial_no=None |
|||
), |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=20, qty_after_transaction=10, |
|||
posting_date="2021-12-03", voucher_type="Stock Entry", |
|||
voucher_no="003", |
|||
has_serial_no=False, serial_no=None |
|||
), |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=10, qty_after_transaction=20, |
|||
posting_date="2021-12-03", voucher_type="Stock Entry", |
|||
voucher_no="004", |
|||
has_serial_no=False, serial_no=None |
|||
) |
|||
] |
|||
|
|||
slots = FIFOSlots(self.filters, sle).generate() |
|||
|
|||
result = slots["Flask Item"] |
|||
queue = result["fifo_queue"] |
|||
|
|||
self.assertEqual(result["qty_after_transaction"], result["total_qty"]) |
|||
self.assertEqual(queue[0][0], 10.0) |
|||
self.assertEqual(queue[1][0], 10.0) |
|||
|
|||
def test_stock_reconciliation(self): |
|||
sle = [ |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=30, qty_after_transaction=30, |
|||
posting_date="2021-12-01", voucher_type="Stock Entry", |
|||
voucher_no="001", |
|||
has_serial_no=False, serial_no=None |
|||
), |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=0, qty_after_transaction=50, |
|||
posting_date="2021-12-02", voucher_type="Stock Reconciliation", |
|||
voucher_no="002", |
|||
has_serial_no=False, serial_no=None |
|||
), |
|||
frappe._dict( |
|||
name="Flask Item", |
|||
actual_qty=(-10), qty_after_transaction=40, |
|||
posting_date="2021-12-03", voucher_type="Stock Entry", |
|||
voucher_no="003", |
|||
has_serial_no=False, serial_no=None |
|||
) |
|||
] |
|||
|
|||
slots = FIFOSlots(self.filters, sle).generate() |
|||
|
|||
result = slots["Flask Item"] |
|||
queue = result["fifo_queue"] |
|||
|
|||
self.assertEqual(result["qty_after_transaction"], result["total_qty"]) |
|||
self.assertEqual(queue[0][0], 20.0) |
|||
self.assertEqual(queue[1][0], 20.0) |
Loading…
Reference in new issue