agris 0.19.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/agents/app-explorer.md +212 -0
- data/.claude/agents/cross-review.md +152 -0
- data/.claude/agents/developer.md +215 -0
- data/.claude/agents/pm.md +401 -0
- data/.claude/agents/qa.md +235 -0
- data/.claude/agents/reviewer.md +185 -0
- data/.claude/agents/spec-preflight.md +167 -0
- data/.claude/agents/spec.md +244 -0
- data/.claude/ai/agent-rules.md +36 -0
- data/.claude/ai/spec-writing-guide.md +43 -0
- data/.claude/commands/app-explorer.md +87 -0
- data/.claude/commands/cross-review.md +91 -0
- data/.claude/commands/developer.md +118 -0
- data/.claude/commands/pm.md +166 -0
- data/.claude/commands/qa.md +111 -0
- data/.claude/commands/reviewer.md +86 -0
- data/.claude/commands/spec.md +172 -0
- data/.github/workflows/ci.yml +62 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +10 -103
- data/.ruby-version +1 -0
- data/CHANGELOG.md +91 -0
- data/CLAUDE.md +98 -0
- data/README.md +9 -1
- data/agris.gemspec +4 -2
- data/docs/RELEASING.md +147 -0
- data/docs/specs/README.md +24 -0
- data/docs/specs/accounts-payables/.gitkeep +0 -0
- data/docs/specs/accounts-receivables/.gitkeep +0 -0
- data/docs/specs/grain/.gitkeep +0 -0
- data/docs/specs/inventory/.gitkeep +0 -0
- data/docs/specs/messages/.gitkeep +0 -0
- data/docs/specs/ot3-257-freight-ticket-reference.md +48 -0
- data/docs/specs/rename-folder.example.md +82 -0
- data/docs/specs/spec-template.md +68 -0
- data/docs/specs/support/.gitkeep +0 -0
- data/lib/agris/api/accounts_payables/disbursements.rb +19 -0
- data/lib/agris/api/accounts_payables/new_disbursement.rb +42 -0
- data/lib/agris/api/accounts_payables.rb +3 -0
- data/lib/agris/api/accounts_receivables/invoices.rb +1 -1
- data/lib/agris/api/accounts_receivables/new_payment.rb +96 -0
- data/lib/agris/api/accounts_receivables/new_payment_remark.rb +38 -0
- data/lib/agris/api/accounts_receivables/payment_post_result.rb +35 -0
- data/lib/agris/api/accounts_receivables/payments.rb +33 -0
- data/lib/agris/api/accounts_receivables.rb +6 -0
- data/lib/agris/api/document_query_response.rb +6 -2
- data/lib/agris/api/grain/contract.rb +3 -3
- data/lib/agris/api/grain/new_ticket.rb +1 -0
- data/lib/agris/api/grain/purchase_contracts.rb +1 -1
- data/lib/agris/api/grain/sales_contracts.rb +1 -1
- data/lib/agris/api/grain.rb +4 -2
- data/lib/agris/api/inventory/delivery_tickets.rb +1 -1
- data/lib/agris/api/inventory/orders.rb +1 -1
- data/lib/agris/api/messages/changed_query_base.rb +1 -0
- data/lib/agris/api/messages/document_query_base.rb +1 -0
- data/lib/agris/api/messages/import.rb +1 -0
- data/lib/agris/api/new_voucher.rb +21 -3
- data/lib/agris/api/post_result.rb +9 -1
- data/lib/agris/client.rb +3 -1
- data/lib/agris/version.rb +1 -1
- data/lib/agris.rb +1 -0
- data/lib/hash.rb +1 -0
- metadata +62 -12
- data/.circleci/config.yml +0 -45
- data/.codeclimate.yml +0 -5
data/docs/RELEASING.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Releasing agris
|
|
2
|
+
|
|
3
|
+
The gem is published to [rubygems.org](https://rubygems.org/gems/agris) as `agris`.
|
|
4
|
+
|
|
5
|
+
Releases are cut with `bundle exec rake release`, which tags, pushes and
|
|
6
|
+
publishes in a single irreversible step. Read [What `rake release` actually
|
|
7
|
+
does](#what-rake-release-actually-does) before running it.
|
|
8
|
+
|
|
9
|
+
## Versioning
|
|
10
|
+
|
|
11
|
+
**One version line, released from `master`.** The version number follows
|
|
12
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) and describes the
|
|
13
|
+
**API** — not which Ruby the gem supports.
|
|
14
|
+
|
|
15
|
+
Ruby support is expressed in the gemspec instead:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
spec.required_ruby_version = '>= 3.1'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Bundler's resolver does the rest. An application on Ruby 3.1 resolves to the
|
|
22
|
+
newest version whose floor it satisfies; one on 3.4 resolves to the newest
|
|
23
|
+
version outright. There is no need for parallel release lines, and consumers do
|
|
24
|
+
not need to pin a version to stay on a supported Ruby.
|
|
25
|
+
|
|
26
|
+
> This replaced an earlier scheme that encoded the supported Ruby in the version
|
|
27
|
+
> number (`0.19.x` for Ruby 3.1, `0.2x` for Ruby 3.3). It fought semver — the
|
|
28
|
+
> version had to step *backwards* at one point — and RubyGems has no concept of
|
|
29
|
+
> release lines anyway: it always serves the highest version as `latest`.
|
|
30
|
+
|
|
31
|
+
### How Ruby changes map to versions
|
|
32
|
+
|
|
33
|
+
| Change | Version impact |
|
|
34
|
+
|--------|----------------|
|
|
35
|
+
| **Adding** support for a newer Ruby (3.5, 4.x) | **None.** Add it to the CI matrix. Patch release only if fixes were needed. |
|
|
36
|
+
| **Dropping** support for an older Ruby (raising `required_ruby_version`) | **Breaking** — major bump. |
|
|
37
|
+
|
|
38
|
+
Everything else is ordinary semver on the public API.
|
|
39
|
+
|
|
40
|
+
Raising the floor is breaking because applications that could install the gem
|
|
41
|
+
yesterday cannot today. Do it deliberately, in its own release.
|
|
42
|
+
|
|
43
|
+
### Keep the floor honest
|
|
44
|
+
|
|
45
|
+
`.github/workflows/ci.yml` runs the suite across **floor, current and next** —
|
|
46
|
+
currently 3.1, 3.3 and 3.4. If you change `required_ruby_version`, change the
|
|
47
|
+
matrix in the same PR, or the declared floor becomes a claim nothing tests.
|
|
48
|
+
|
|
49
|
+
Note that the matrix job names are required status checks on `master`
|
|
50
|
+
(`test (3.1)`, `test (3.3)`, `test (3.4)`, `lint`). Changing the matrix changes
|
|
51
|
+
those names, so branch protection must be updated to match — otherwise every
|
|
52
|
+
PR blocks on a check that can never report.
|
|
53
|
+
|
|
54
|
+
## Pre-release checklist
|
|
55
|
+
|
|
56
|
+
1. **Be on `master`**, up to date, with everything merged that you intend to
|
|
57
|
+
ship. `rake release` tags and pushes whatever branch you are standing on —
|
|
58
|
+
it does not check.
|
|
59
|
+
2. **Working tree is clean.** `rake release` aborts otherwise.
|
|
60
|
+
3. **Full suite is green:**
|
|
61
|
+
```sh
|
|
62
|
+
bundle exec rake # specs + rubocop
|
|
63
|
+
```
|
|
64
|
+
4. **Version is bumped** in `lib/agris/version.rb`, and the bump is committed.
|
|
65
|
+
5. **`required_ruby_version` is correct** for what you are shipping, and the CI
|
|
66
|
+
matrix covers it.
|
|
67
|
+
6. **The tag does not already exist:**
|
|
68
|
+
```sh
|
|
69
|
+
git tag --list 'v*' | sort -V | tail
|
|
70
|
+
```
|
|
71
|
+
7. **RubyGems credentials are present** — see [Credentials](#credentials).
|
|
72
|
+
|
|
73
|
+
## Cutting the release
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
git checkout master
|
|
77
|
+
git pull
|
|
78
|
+
# bump lib/agris/version.rb, commit, and merge via PR
|
|
79
|
+
bundle exec rake # confirm green
|
|
80
|
+
bundle exec rake release
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If your account has MFA enabled, `gem push` prompts for an OTP code partway
|
|
84
|
+
through — do not walk away from the terminal.
|
|
85
|
+
|
|
86
|
+
## What `rake release` actually does
|
|
87
|
+
|
|
88
|
+
Provided by `bundler/gem_tasks`. In order:
|
|
89
|
+
|
|
90
|
+
1. **Guards the working tree** — aborts with "There are files that need to be
|
|
91
|
+
committed first" if anything is modified or staged.
|
|
92
|
+
2. **Checks whether `v<version>` is already tagged** — if so, skips tagging and
|
|
93
|
+
continues to publish anyway.
|
|
94
|
+
3. **Creates the tag** — `git tag -m "Version <version>" v<version>`.
|
|
95
|
+
4. **Builds the gem** into `pkg/agris-<version>.gem`.
|
|
96
|
+
5. **Pushes to git** — the **current branch** and the new tag, to that branch's
|
|
97
|
+
configured remote (`origin` by default).
|
|
98
|
+
6. **Publishes to rubygems.org** — `gem push pkg/agris-<version>.gem`.
|
|
99
|
+
|
|
100
|
+
Steps 5 and 6 reach outside the repository. Step 6 is effectively permanent.
|
|
101
|
+
|
|
102
|
+
If tagging succeeds but a later step fails, the tag is removed automatically.
|
|
103
|
+
If the *push* succeeds and publishing fails, the tag is already public — delete
|
|
104
|
+
it manually before retrying:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
git tag -d v<version>
|
|
108
|
+
git push origin :refs/tags/v<version>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Credentials
|
|
112
|
+
|
|
113
|
+
`gem push` reads `~/.gem/credentials`. If you have never pushed from this
|
|
114
|
+
machine:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
gem signin
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The gemspec sets no `allowed_push_host`, so pushes go to rubygems.org. You must
|
|
121
|
+
be an owner of the `agris` gem — check with `gem owner agris`.
|
|
122
|
+
|
|
123
|
+
## After releasing
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
gem list agris --remote --all # new version is listed
|
|
127
|
+
git ls-remote --tags origin # tag reached the remote
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Confirm the published gem declares the Ruby floor you expect:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
gem specification agris -r required_ruby_version
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## If a release goes wrong
|
|
137
|
+
|
|
138
|
+
A published version can be yanked, but the version number is **burned
|
|
139
|
+
permanently** — it can never be reused:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
gem yank agris -v <version>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Yanking removes it from resolution for new installs; it does not recall it from
|
|
146
|
+
anyone who already has it. Prefer releasing a fixed higher version over yanking
|
|
147
|
+
wherever possible.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Specs
|
|
2
|
+
|
|
3
|
+
Specs in this repo are **build prompts**: short, point-in-time briefs for one PR. The code is
|
|
4
|
+
the source of truth; a prompt records what a slice was asked to deliver and is not updated
|
|
5
|
+
after review. Divergence between prompt and merged code is an expected record of what review
|
|
6
|
+
corrected.
|
|
7
|
+
|
|
8
|
+
Standing facts (Agris behaviour, business rules, spike results) live in Jira and Confluence and
|
|
9
|
+
are cited, not repeated. A prompt states only what is specific to its slice.
|
|
10
|
+
|
|
11
|
+
## Writing one
|
|
12
|
+
1. Name the file for the Jira task the PR delivers: `docs/specs/<key>-<slug>.md`
|
|
13
|
+
(e.g. `ot3-257-freight-ticket-reference.md`).
|
|
14
|
+
2. Keep it to roughly 25–45 lines: **Goal** (2–3 sentences), **Scope** stated positively
|
|
15
|
+
("Exactly: …" plus one "Later: …" line), **Rules** (R#, MUST / MUST NOT), **Acceptance
|
|
16
|
+
criteria** (AC#, the complete test list), and **Slice-specific notes** for gotchas.
|
|
17
|
+
3. Note the diff budget (≤200 lines) and split before dispatch if the estimate is over.
|
|
18
|
+
4. Commit it with the PR as a `docs(specs):` commit and paste it into the PR description
|
|
19
|
+
in a collapsed `<details>` block titled "Build prompt (frozen — not updated after review)".
|
|
20
|
+
5. Cite R# and AC# in commit bodies and the PR's test checklist. Do not put spec IDs or
|
|
21
|
+
paths in code or test comments.
|
|
22
|
+
|
|
23
|
+
`spec-template.md` and `rename-folder.example.md` are the retired long-form house format,
|
|
24
|
+
kept for reference only. The area folders (`grain/`, `inventory/`, …) are unused by prompts.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Build Prompt — OT3-257: ACPV3 freight ticket reference detail
|
|
2
|
+
|
|
3
|
+
Jira: OT3-255 task OT3-257 · 2026-09-16 · Budget ≤200.
|
|
4
|
+
Condensed after the build from the house-format spec this PR first carried; rules and ACs
|
|
5
|
+
match the shipped code. Standing facts live in Jira OT3-256 (staging spike, 2026-09-14) and
|
|
6
|
+
the BRD "Freight Voucher via Agris API" (Confluence, SD space) and are not repeated here.
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
Let a consumer post an AGRIS accounts-payable freight voucher (type FV) that references the
|
|
10
|
+
grain ticket it pays. AGRIS 22.3.0 SU5 or later (AGS-17887) stamps the voucher number on the
|
|
11
|
+
ticket and drops it from the unpaid Freight Expenses population. The gem gains the ACPV3
|
|
12
|
+
detail record; contracto wires it into vouchers in OT3-259.
|
|
13
|
+
|
|
14
|
+
## Scope
|
|
15
|
+
Exactly: `Agris::Api::NewVoucher::FreightTicketReferenceDetail` in `lib/agris/api/new_voucher.rb`,
|
|
16
|
+
its RSpec file, a `NewVoucher` RSpec file for mixed details, this prompt, and an `Unreleased`
|
|
17
|
+
changelog line. Later: the 1.1.0 bump and dated changelog heading (release PR); consumer-side
|
|
18
|
+
rules in contracto's `docs/specs/freight-conventions.md` (OT3-258).
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
- R1 — includes `XmlModel`; `ATTRIBUTE_NAMES` is exactly `in_out_code`, `ticket_location`,
|
|
22
|
+
`ticket_number`, `freight_amount`, `record_type`; `attr_reader(*ATTRIBUTE_NAMES)` as on
|
|
23
|
+
`NewVoucher` and `NewDisbursement`.
|
|
24
|
+
- R2 — `record_type` is always `'ACPV3'`; a value passed to the constructor is overwritten.
|
|
25
|
+
- R3 — `to_xml_hash` emits `:@recordtype => 'ACPV3'` plus `:@inoutcode`, `:@ticketlocation`,
|
|
26
|
+
`:@ticketnumber`, `:@freightamount` for whichever attributes were supplied, values untouched.
|
|
27
|
+
An omitted attribute emits no key (existing `XmlModel` behaviour; AGRIS decides validity).
|
|
28
|
+
- R4 — `NewVoucher#add_detail` needs no change: a voucher holding a `GeneralLedgerDetail` and
|
|
29
|
+
this detail serializes both, in insertion order, each under its own record type.
|
|
30
|
+
- R5 — MUST NOT validate AGRIS-side rules (ACPV3 sum equals voucher amount, shipper equals
|
|
31
|
+
remit-to, an ACPV2 line present). AGRIS reports those in-band via `PostResult#reject_reasons`.
|
|
32
|
+
|
|
33
|
+
## Acceptance criteria (the complete test list)
|
|
34
|
+
- AC-1 — `record_type` defaults to ACPV3; `record_type: 'OTHER'` still reads and serializes ACPV3.
|
|
35
|
+
- AC-2 — all four attributes supplied (I / 051 / 0028786 / 230.85, the spike's ticket): readers
|
|
36
|
+
return the inputs; `to_xml_hash` equals exactly the five-key hash.
|
|
37
|
+
- AC-3 — only `ticket_number` supplied: the other readers return nil; hash keys are exactly
|
|
38
|
+
`:@ticketnumber` and `:@recordtype`.
|
|
39
|
+
- AC-4 — `ATTRIBUTE_NAMES` lists exactly the five names.
|
|
40
|
+
- AC-5 — `NewVoucher` with one GL detail then one ACPV3 detail: `details` in insertion order,
|
|
41
|
+
record types `%w(ACPV2 ACPV3)`, header `to_xml_hash` excludes the details.
|
|
42
|
+
|
|
43
|
+
## Slice-specific notes
|
|
44
|
+
- The two older detail classes have no readers and are left alone.
|
|
45
|
+
- No spec IDs or paths in code or test comments (Coding Style Rules §2.4); traceability is this
|
|
46
|
+
prompt plus the PR checklist.
|
|
47
|
+
- Wire shape confirmed by rendering the real `create_voucher` payload: the ACPV3 element lands
|
|
48
|
+
flat inside `<details>` beside ACPV0 and ACPV2.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Spec: Rename Folder (Example)
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Allow a user to rename an existing folder.
|
|
5
|
+
|
|
6
|
+
## Non Goals
|
|
7
|
+
- Moving folders
|
|
8
|
+
- Renaming multiple folders
|
|
9
|
+
|
|
10
|
+
## Definitions
|
|
11
|
+
- Folder: user-owned container for documents.
|
|
12
|
+
|
|
13
|
+
## Interfaces
|
|
14
|
+
Endpoint:
|
|
15
|
+
`PATCH /folders/{id}`
|
|
16
|
+
|
|
17
|
+
Input:
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"name": "string"
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Output:
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"id": "string",
|
|
28
|
+
"name": "string"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Rules
|
|
33
|
+
R1: Folder name must be 1-80 characters.
|
|
34
|
+
R2: Leading and trailing whitespace must be trimmed.
|
|
35
|
+
R3: If name is unchanged, return success without modification.
|
|
36
|
+
R4: User must have folder write permission.
|
|
37
|
+
|
|
38
|
+
## State Model
|
|
39
|
+
- Existing folder name -> renamed folder name (or unchanged for idempotent update).
|
|
40
|
+
|
|
41
|
+
## Edge Cases
|
|
42
|
+
E1: Folder does not exist -> return 404.
|
|
43
|
+
E2: Invalid name -> return 422.
|
|
44
|
+
E3: Unauthorized -> return 403.
|
|
45
|
+
|
|
46
|
+
## Acceptance Criteria
|
|
47
|
+
AC-1: User can rename an existing folder with a valid name (1-80 chars).
|
|
48
|
+
AC-2: System trims whitespace and handles idempotent renames.
|
|
49
|
+
AC-3: Unauthorized or invalid requests are rejected with appropriate status codes.
|
|
50
|
+
|
|
51
|
+
## Acceptance Tests
|
|
52
|
+
AT1
|
|
53
|
+
Given a valid folder id
|
|
54
|
+
When a valid name is submitted
|
|
55
|
+
Then the folder name is updated.
|
|
56
|
+
Covers: R1, R2
|
|
57
|
+
|
|
58
|
+
AT2
|
|
59
|
+
Given an unchanged folder name
|
|
60
|
+
When request is submitted
|
|
61
|
+
Then return success without modification.
|
|
62
|
+
Covers: R3
|
|
63
|
+
|
|
64
|
+
AT3
|
|
65
|
+
Given a user without permission
|
|
66
|
+
When request is submitted
|
|
67
|
+
Then return 403.
|
|
68
|
+
Covers: R4
|
|
69
|
+
|
|
70
|
+
## Observability
|
|
71
|
+
- Log validation failures with request id and rule ID.
|
|
72
|
+
- Emit metric for rename success/failure counts.
|
|
73
|
+
|
|
74
|
+
## Implementation Decisions
|
|
75
|
+
|
|
76
|
+
| Date | Decision | Rationale |
|
|
77
|
+
|------|----------|-----------|
|
|
78
|
+
|
|
79
|
+
## Change Log
|
|
80
|
+
|
|
81
|
+
| Date | Change | Affected IDs | Rationale |
|
|
82
|
+
|------|--------|-------------|-----------|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!-- See rename-folder.example.md for a completed example of this template -->
|
|
2
|
+
|
|
3
|
+
# Spec: <Feature Name>
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
Short description of the feature's purpose and value.
|
|
7
|
+
|
|
8
|
+
## Non Goals
|
|
9
|
+
Explicitly excluded behavior. List what this spec intentionally does NOT cover.
|
|
10
|
+
|
|
11
|
+
## Definitions
|
|
12
|
+
Key domain terms used in this spec. Define any term a new team member might misunderstand.
|
|
13
|
+
|
|
14
|
+
## Interfaces
|
|
15
|
+
Inputs, outputs, APIs, or UI events. Include request/response schemas where applicable.
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
<!-- Rules describe system behavior at implementation level.
|
|
19
|
+
Each rule should be specific enough to test.
|
|
20
|
+
Bad: "User can log in."
|
|
21
|
+
Good: "POST /api/auth/login with valid credentials returns 200 and a session token (R1)." -->
|
|
22
|
+
|
|
23
|
+
R1: <rule>
|
|
24
|
+
R2: <rule>
|
|
25
|
+
|
|
26
|
+
## State Model
|
|
27
|
+
If the system has state transitions, describe them here. Otherwise remove this section.
|
|
28
|
+
|
|
29
|
+
## Edge Cases
|
|
30
|
+
E1: <edge case and expected behavior>
|
|
31
|
+
E2: <edge case and expected behavior>
|
|
32
|
+
|
|
33
|
+
## Acceptance Criteria
|
|
34
|
+
<!-- ACs describe observable outcomes a stakeholder can verify.
|
|
35
|
+
Rules are implementation-facing; ACs are user-facing.
|
|
36
|
+
Example: "AC-1: User sees a green checkmark when password meets all strength requirements." -->
|
|
37
|
+
|
|
38
|
+
AC-1: <criterion>
|
|
39
|
+
AC-2: <criterion>
|
|
40
|
+
|
|
41
|
+
## Acceptance Tests
|
|
42
|
+
<!-- Each AT must have a Covers: field listing the R# or E# it validates.
|
|
43
|
+
An AT can cover multiple rules. -->
|
|
44
|
+
|
|
45
|
+
AT1
|
|
46
|
+
Given <context>
|
|
47
|
+
When <action>
|
|
48
|
+
Then <outcome>
|
|
49
|
+
Covers: R1
|
|
50
|
+
|
|
51
|
+
AT2
|
|
52
|
+
Given <context>
|
|
53
|
+
When <action>
|
|
54
|
+
Then <outcome>
|
|
55
|
+
Covers: R2
|
|
56
|
+
|
|
57
|
+
## Observability
|
|
58
|
+
Logs, metrics, alerts (optional).
|
|
59
|
+
|
|
60
|
+
## Implementation Decisions
|
|
61
|
+
|
|
62
|
+
| Date | Decision | Rationale |
|
|
63
|
+
|------|----------|-----------|
|
|
64
|
+
|
|
65
|
+
## Change Log
|
|
66
|
+
|
|
67
|
+
| Date | Change | Affected IDs | Rationale |
|
|
68
|
+
|------|--------|-------------|-----------|
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsPayables
|
|
6
|
+
module Disbursements
|
|
7
|
+
def create_disbursement(disbursement)
|
|
8
|
+
response = @request.process_message(
|
|
9
|
+
Gyoku.xml(xml: context_hash),
|
|
10
|
+
82_320,
|
|
11
|
+
create_post_payload_xml([disbursement.to_xml_hash])
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
PostResult.new(response)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsPayables
|
|
6
|
+
class NewDisbursement
|
|
7
|
+
include XmlModel
|
|
8
|
+
|
|
9
|
+
ATTRIBUTE_NAMES = %w(
|
|
10
|
+
applied_date
|
|
11
|
+
apply_payment_on_account
|
|
12
|
+
bank_code
|
|
13
|
+
cash_source
|
|
14
|
+
check_location
|
|
15
|
+
check_number
|
|
16
|
+
currency_code
|
|
17
|
+
discount_amount
|
|
18
|
+
exchange_rate
|
|
19
|
+
exchange_rate_date
|
|
20
|
+
misc_reference
|
|
21
|
+
name_id
|
|
22
|
+
payment_amount
|
|
23
|
+
payment_date
|
|
24
|
+
prepayment_reference
|
|
25
|
+
prepayment_type
|
|
26
|
+
record_type
|
|
27
|
+
reserved
|
|
28
|
+
voucher_location
|
|
29
|
+
voucher_number
|
|
30
|
+
).freeze
|
|
31
|
+
|
|
32
|
+
attr_reader(*ATTRIBUTE_NAMES)
|
|
33
|
+
|
|
34
|
+
def initialize(hash = {})
|
|
35
|
+
super
|
|
36
|
+
|
|
37
|
+
@record_type = 'ACPD0'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Agris
|
|
4
4
|
module Api
|
|
5
5
|
module AccountsPayables
|
|
6
|
+
autoload :Disbursements, 'agris/api/accounts_payables/disbursements'
|
|
7
|
+
autoload :NewDisbursement, 'agris/api/accounts_payables/new_disbursement'
|
|
8
|
+
|
|
6
9
|
module Vouchers
|
|
7
10
|
def create_voucher(voucher)
|
|
8
11
|
response = @request.process_message(
|
|
@@ -17,7 +17,7 @@ module Agris
|
|
|
17
17
|
)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def invoices_changed_since(datetime, detail
|
|
20
|
+
def invoices_changed_since(datetime, detail: false)
|
|
21
21
|
extract_documents(
|
|
22
22
|
Messages::QueryChangedInvoices.new(datetime, detail),
|
|
23
23
|
Agris::Api::AccountsReceivables::Invoice
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
class NewPayment
|
|
7
|
+
include XmlModel
|
|
8
|
+
|
|
9
|
+
RECEIVE_ON_ACCOUNT = '0'
|
|
10
|
+
APPLY_TO_INVOICE = 'E'
|
|
11
|
+
|
|
12
|
+
ATTRIBUTE_NAMES = %w(
|
|
13
|
+
applied_date
|
|
14
|
+
apply_receive_on_acct
|
|
15
|
+
bank_code
|
|
16
|
+
cash_source
|
|
17
|
+
check_number
|
|
18
|
+
discount_amount
|
|
19
|
+
invoice_line_pricing_no
|
|
20
|
+
invoice_location
|
|
21
|
+
name_id
|
|
22
|
+
payment_amount
|
|
23
|
+
payment_date
|
|
24
|
+
receipt_location
|
|
25
|
+
receipt_number
|
|
26
|
+
record_type
|
|
27
|
+
).freeze
|
|
28
|
+
|
|
29
|
+
attr_reader(*ATTRIBUTE_NAMES)
|
|
30
|
+
|
|
31
|
+
# Agris allocates the receipt number, so it is left for the response.
|
|
32
|
+
# An applied date or invoice on an unapplied receipt is rejected.
|
|
33
|
+
def self.receive(attributes = {})
|
|
34
|
+
new(
|
|
35
|
+
attributes.merge(
|
|
36
|
+
apply_receive_on_acct: RECEIVE_ON_ACCOUNT,
|
|
37
|
+
applied_date: '',
|
|
38
|
+
invoice_line_pricing_no: ''
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Draws down the unapplied balance of a receipt Agris already
|
|
44
|
+
# allocated; it cannot create one.
|
|
45
|
+
def self.apply(attributes = {})
|
|
46
|
+
new(attributes.merge(apply_receive_on_acct: APPLY_TO_INVOICE))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def initialize(attributes = {})
|
|
50
|
+
super
|
|
51
|
+
|
|
52
|
+
@record_type = 'ACRR0'
|
|
53
|
+
@remarks = []
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_remark(remark)
|
|
57
|
+
@remarks << remark
|
|
58
|
+
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def remarks
|
|
63
|
+
@remarks || []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def records
|
|
67
|
+
[self] + remarks
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def for_allocation(allocation)
|
|
71
|
+
allocated = self.class.new(hash.merge(allocation_hash(allocation)))
|
|
72
|
+
|
|
73
|
+
remarks.each do |remark|
|
|
74
|
+
allocated.add_remark(remark.for_allocation(allocation))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
allocated
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def xml_ignore_attributes
|
|
81
|
+
[:remarks]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
def allocation_hash(allocation)
|
|
87
|
+
{
|
|
88
|
+
bank_code: allocation.bank_code,
|
|
89
|
+
receipt_location: allocation.receipt_location,
|
|
90
|
+
receipt_number: allocation.receipt_number
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
class NewPaymentRemark
|
|
7
|
+
include XmlModel
|
|
8
|
+
|
|
9
|
+
ATTRIBUTE_NAMES = %w(
|
|
10
|
+
bank_code
|
|
11
|
+
receipt_location
|
|
12
|
+
receipt_number
|
|
13
|
+
record_type
|
|
14
|
+
remark_number
|
|
15
|
+
remark_value
|
|
16
|
+
).freeze
|
|
17
|
+
|
|
18
|
+
attr_reader(*ATTRIBUTE_NAMES)
|
|
19
|
+
|
|
20
|
+
def initialize(attributes = {})
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
@record_type = 'ACRR1'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def for_allocation(allocation)
|
|
27
|
+
self.class.new(
|
|
28
|
+
hash.merge(
|
|
29
|
+
bank_code: allocation.bank_code,
|
|
30
|
+
receipt_location: allocation.receipt_location,
|
|
31
|
+
receipt_number: allocation.receipt_number
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
# The posted receipt comes back in `document` as a fixed-width triple:
|
|
7
|
+
# receipt location (3), receipt number (6), bank code (2).
|
|
8
|
+
class PaymentPostResult < PostResult
|
|
9
|
+
def receipt_location
|
|
10
|
+
document_field(0, 3)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def receipt_number
|
|
14
|
+
document_field(3, 6)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def bank_code
|
|
18
|
+
document_field(9, 2)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A rejected post still returns the location and bank code, so only the
|
|
22
|
+
# receipt number tells you whether Agris allocated a receipt.
|
|
23
|
+
def allocated?
|
|
24
|
+
!receipt_number.empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def document_field(offset, length)
|
|
30
|
+
document_number.to_s[offset, length].to_s.strip
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
module Payments
|
|
7
|
+
def create_payment(payment)
|
|
8
|
+
post_payment(payment)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Agris will not accept a receipt and its applications in one message,
|
|
12
|
+
# so each application posts against the receipt Agris allocated.
|
|
13
|
+
def apply_payment(payment, allocation)
|
|
14
|
+
post_payment(payment.for_allocation(allocation))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
protected
|
|
18
|
+
|
|
19
|
+
def post_payment(payment)
|
|
20
|
+
message = Messages::Import.new(payment)
|
|
21
|
+
|
|
22
|
+
response = @request.process_message(
|
|
23
|
+
Gyoku.xml(xml: context_hash),
|
|
24
|
+
message.message_number,
|
|
25
|
+
message.to_xml
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
PaymentPostResult.new(response)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -4,6 +4,12 @@ module Agris
|
|
|
4
4
|
module AccountsReceivables
|
|
5
5
|
autoload :Invoice, 'agris/api/accounts_receivables/invoice'
|
|
6
6
|
autoload :Invoices, 'agris/api/accounts_receivables/invoices'
|
|
7
|
+
autoload :NewPayment, 'agris/api/accounts_receivables/new_payment'
|
|
8
|
+
autoload :NewPaymentRemark,
|
|
9
|
+
'agris/api/accounts_receivables/new_payment_remark'
|
|
10
|
+
autoload :PaymentPostResult,
|
|
11
|
+
'agris/api/accounts_receivables/payment_post_result'
|
|
12
|
+
autoload :Payments, 'agris/api/accounts_receivables/payments'
|
|
7
13
|
autoload :SpecificInvoiceExtract,
|
|
8
14
|
'agris/api/accounts_receivables/specific_invoice_extract'
|
|
9
15
|
end
|